diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/ClickCaptchaView.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/ClickCaptchaView.kt index 2a3c047..050da2f 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/composables/ClickCaptchaView.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/ClickCaptchaView.kt @@ -154,7 +154,7 @@ fun ClickCaptchaDialog( onDismissRequest() }, title = { - Text("Captcha") + Text(stringResource(R.string.captcha)) }, text = { Column { @@ -163,14 +163,14 @@ fun ClickCaptchaDialog( .fillMaxWidth() ) { ClickCaptchaView( - captchaData = captchaData!!, + captchaData = captchaData, onPositionClicked = onPositionClicked ) } Spacer(modifier = Modifier.height(16.dp)) ActionButton( - text = "Refresh", + text = stringResource(R.string.refresh), modifier = Modifier .fillMaxWidth(), ) { diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/toolbar/CollapsingToolbarScaffold.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/toolbar/CollapsingToolbarScaffold.kt index a93d3c0..13b0f62 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/composables/toolbar/CollapsingToolbarScaffold.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/toolbar/CollapsingToolbarScaffold.kt @@ -96,7 +96,7 @@ fun CollapsingToolbarScaffold( toolbarModifier: Modifier = Modifier, toolbarClipToBounds: Boolean = true, toolbarScrollable: Boolean = false, - toolbar: @Composable CollapsingToolbarScope.() -> Unit, + toolbar: @Composable CollapsingToolbarScope.(ScrollState) -> Unit, body: @Composable CollapsingToolbarScaffoldScope.() -> Unit ) { val flingBehavior = ScrollableDefaults.flingBehavior() @@ -122,7 +122,7 @@ fun CollapsingToolbarScaffold( toolbarState, toolbarScrollState ) - toolbar() + toolbar(toolbarScrollState) } CollapsingToolbarScaffoldScopeInstance.body() diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/toolbar/ToolbarWithFabScaffold.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/toolbar/ToolbarWithFabScaffold.kt index 4034204..4e3d946 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/composables/toolbar/ToolbarWithFabScaffold.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/toolbar/ToolbarWithFabScaffold.kt @@ -1,5 +1,6 @@ package com.aiosman.riderpro.ui.composables.toolbar +import androidx.compose.foundation.ScrollState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.SubcomposeLayout @@ -14,7 +15,7 @@ fun ToolbarWithFabScaffold( scrollStrategy: ScrollStrategy, toolbarModifier: Modifier = Modifier, toolbarClipToBounds: Boolean = true, - toolbar: @Composable CollapsingToolbarScope.() -> Unit, + toolbar: @Composable CollapsingToolbarScope.(ScrollState) -> Unit, toolbarScrollable: Boolean = false, fab: @Composable () -> Unit, fabPosition: FabPosition = FabPosition.End, diff --git a/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerList.kt b/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerList.kt index c2ed905..318ff8c 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerList.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerList.kt @@ -1,12 +1,19 @@ package com.aiosman.riderpro.ui.follower import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -16,15 +23,21 @@ import com.aiosman.riderpro.ui.comment.NoticeScreenHeader import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout import kotlinx.coroutines.launch +@OptIn(ExperimentalMaterialApi::class) @Composable fun FollowerListScreen(userId: Int) { val model = FollowerListViewModel val scope = rememberCoroutineScope() + val refreshState = rememberPullRefreshState(model.isLoading, onRefresh = { + model.loadData(userId, true) + }) LaunchedEffect(Unit) { model.loadData(userId) } + StatusBarMaskLayout( - modifier = Modifier.padding(horizontal = 16.dp) + modifier = Modifier + .padding(horizontal = 16.dp) ) { var dataFlow = model.usersFlow var users = dataFlow.collectAsLazyPagingItems() @@ -35,27 +48,39 @@ fun FollowerListScreen(userId: Int) { ) { NoticeScreenHeader(stringResource(R.string.followers_upper), moreIcon = false) } - LazyColumn( - modifier = Modifier.weight(1f) + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .pullRefresh(refreshState) ) { - items(users.itemCount) { index -> - users[index]?.let { user -> - FollowItem( - avatar = user.avatar, - nickname = user.nickName, - userId = user.id, - isFollowing = user.isFollowing - ) { - scope.launch { - if (user.isFollowing) { - model.unFollowUser(user.id) - } else { - model.followUser(user.id) + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + items(users.itemCount) { index -> + users[index]?.let { user -> + FollowItem( + avatar = user.avatar, + nickname = user.nickName, + userId = user.id, + isFollowing = user.isFollowing + ) { + scope.launch { + if (user.isFollowing) { + model.unFollowUser(user.id) + } else { + model.followUser(user.id) + } } } } } } + PullRefreshIndicator( + refreshing = model.isLoading, + state = refreshState, + modifier = Modifier.align(Alignment.TopCenter) + ) } } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerListViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerListViewModel.kt index 78aab56..d8ee08f 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerListViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerListViewModel.kt @@ -23,10 +23,12 @@ object FollowerListViewModel : ViewModel() { private val _usersFlow = MutableStateFlow>(PagingData.empty()) val usersFlow = _usersFlow.asStateFlow() private var userId by mutableStateOf(null) - fun loadData(id: Int) { - if (userId == id) { + var isLoading by mutableStateOf(false) + fun loadData(id: Int,force : Boolean = false) { + if (userId == id && !force) { return } + isLoading = true userId = id viewModelScope.launch { Pager( @@ -41,6 +43,7 @@ object FollowerListViewModel : ViewModel() { _usersFlow.value = it } } + isLoading = false } private fun updateIsFollow(id: Int, isFollow: Boolean = true) { diff --git a/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowingList.kt b/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowingList.kt index 201170c..fc43087 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowingList.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowingList.kt @@ -1,12 +1,18 @@ package com.aiosman.riderpro.ui.follower import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -16,10 +22,14 @@ import com.aiosman.riderpro.ui.comment.NoticeScreenHeader import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout import kotlinx.coroutines.launch +@OptIn(ExperimentalMaterialApi::class) @Composable fun FollowingListScreen(userId: Int) { val model = FollowingListViewModel val scope = rememberCoroutineScope() + val refreshState = rememberPullRefreshState(model.isLoading, onRefresh = { + model.loadData(userId, true) + }) LaunchedEffect(Unit) { model.loadData(userId) } @@ -35,27 +45,39 @@ fun FollowingListScreen(userId: Int) { ) { NoticeScreenHeader(stringResource(R.string.following_upper), moreIcon = false) } - LazyColumn( - modifier = Modifier.weight(1f) + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .pullRefresh(refreshState) ) { - items(users.itemCount) { index -> - users[index]?.let { user -> - FollowItem( - avatar = user.avatar, - nickname = user.nickName, - userId = user.id, - isFollowing = user.isFollowing - ) { - scope.launch { - if (user.isFollowing) { - model.unfollowUser(user.id) - } else { - model.followUser(user.id) + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + items(users.itemCount) { index -> + users[index]?.let { user -> + FollowItem( + avatar = user.avatar, + nickname = user.nickName, + userId = user.id, + isFollowing = user.isFollowing + ) { + scope.launch { + if (user.isFollowing) { + model.unfollowUser(user.id) + } else { + model.followUser(user.id) + } } } } } } + PullRefreshIndicator( + refreshing = model.isLoading, + state = refreshState, + modifier = Modifier.align(Alignment.TopCenter) + ) } } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowingListViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowingListViewModel.kt index 7aae147..cbd709d 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowingListViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowingListViewModel.kt @@ -21,9 +21,14 @@ import kotlinx.coroutines.launch object FollowingListViewModel : ViewModel() { private val userService = UserServiceImpl() private val _usersFlow = MutableStateFlow>(PagingData.empty()) + var isLoading by mutableStateOf(false) val usersFlow = _usersFlow.asStateFlow() private var userId by mutableStateOf(null) - fun loadData(id: Int) { + fun loadData(id: Int, force: Boolean = false) { + if (userId == id && !force) { + return + } + isLoading = true userId = id viewModelScope.launch { Pager( @@ -38,6 +43,7 @@ object FollowingListViewModel : ViewModel() { _usersFlow.value = it } } + isLoading = false } private fun updateIsFollow(id: Int, isFollow: Boolean = true) { @@ -62,7 +68,7 @@ object FollowingListViewModel : ViewModel() { updateIsFollow(userId, false) } - fun ResetModel(){ + fun ResetModel() { userId = null } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/MyProfileViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/MyProfileViewModel.kt index db800c7..dedbfc8 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/MyProfileViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/MyProfileViewModel.kt @@ -23,7 +23,6 @@ import com.aiosman.riderpro.entity.MomentEntity import com.aiosman.riderpro.entity.MomentPagingSource import com.aiosman.riderpro.entity.MomentRemoteDataSource import com.aiosman.riderpro.entity.MomentServiceImpl -import com.aiosman.riderpro.ui.post.NewPostViewModel.uriToFile import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest @@ -43,7 +42,6 @@ object MyProfileViewModel : ViewModel() { val profile = accountService.getMyAccountProfile() MyProfileViewModel.profile = profile } - fun loadProfile(pullRefresh: Boolean = false) { if (!firstLoad && !pullRefresh) return viewModelScope.launch { diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt deleted file mode 100644 index 79158ed..0000000 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profile.kt +++ /dev/null @@ -1,483 +0,0 @@ -package com.aiosman.riderpro.ui.index.tabs.profile - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.net.Uri -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid -import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells -import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.shadow -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.paging.PagingData -import androidx.paging.compose.collectAsLazyPagingItems -import com.aiosman.riderpro.LocalNavController -import com.aiosman.riderpro.R -import com.aiosman.riderpro.entity.AccountProfileEntity -import com.aiosman.riderpro.entity.MomentEntity -import com.aiosman.riderpro.ui.NavigationRoute -import com.aiosman.riderpro.ui.composables.CustomAsyncImage -import com.aiosman.riderpro.ui.composables.DropdownMenu -import com.aiosman.riderpro.ui.composables.MenuItem -import com.aiosman.riderpro.ui.composables.StatusBarSpacer -import com.aiosman.riderpro.ui.index.tabs.profile.composable.EmptyMomentPostUnit -import com.aiosman.riderpro.ui.index.tabs.profile.composable.GalleryItem -import com.aiosman.riderpro.ui.index.tabs.profile.composable.MomentPostUnit -import com.aiosman.riderpro.ui.index.tabs.profile.composable.OtherProfileAction -import com.aiosman.riderpro.ui.index.tabs.profile.composable.SelfProfileAction -import com.aiosman.riderpro.ui.index.tabs.profile.composable.UserContentPageIndicator -import com.aiosman.riderpro.ui.index.tabs.profile.composable.UserItem -import com.aiosman.riderpro.ui.modifiers.noRippleClickable -import com.aiosman.riderpro.ui.post.NewPostViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun Profile( - onUpdateBanner: ((Uri, Context) -> Unit)? = null, - profile: AccountProfileEntity? = null, - onLogout: () -> Unit = {}, - onFollowClick: () -> Unit = {}, - onChatClick: () -> Unit = {}, - sharedFlow: SharedFlow> = MutableStateFlow>( - PagingData.empty() - ).asStateFlow(), - isSelf: Boolean = true -) { - Box( - modifier = Modifier.background(Color(0xffFFFFFF)) - ) { - val userHeight: Int = 187 - val bannerHeight = 500 - var headerBannerMaxHeight: Int = userHeight + bannerHeight - val headerBannerMinHeight = 100 - val speedFactor = 0.75f - var currentHeaderHeight by rememberSaveable { mutableStateOf(headerBannerMaxHeight) } - var scrollState = rememberLazyListState() - var gridScrollState = rememberLazyStaggeredGridState() - var pagerState = rememberPagerState(pageCount = { 2 }) - val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() - val context = LocalContext.current - var expanded by remember { mutableStateOf(false) } - val scope = rememberCoroutineScope() - val navController = LocalNavController.current - val moments = sharedFlow.collectAsLazyPagingItems() - val pickBannerImageLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.StartActivityForResult() - ) { result -> - if (result.resultCode == Activity.RESULT_OK) { - val uri = result.data?.data - uri?.let { - onUpdateBanner?.invoke(it, context) - } - } - } - val parentScrollConnection = remember { - object : NestedScrollConnection { - override fun onPreScroll( - available: Offset, - source: NestedScrollSource - ): Offset { - val delta = (available.y * speedFactor).toInt() - // 如果是向下滑动,未滑动到列表顶部,则不展开头部 - if (pagerState.currentPage == 0) { - if (delta > 0 && (gridScrollState.firstVisibleItemIndex > 0 || gridScrollState.firstVisibleItemScrollOffset > 0)) { - return Offset.Zero - } - } - if (pagerState.currentPage == 1) { - if (delta > 0 && (scrollState.firstVisibleItemIndex > 0 || scrollState.firstVisibleItemScrollOffset > 0)) { - return Offset.Zero - } - } - - // 计算新的高度 - val newHeight = currentHeaderHeight + delta - val previousHeight = currentHeaderHeight - val newCurrentHeaderHeight = - newHeight.coerceIn(headerBannerMinHeight, headerBannerMaxHeight) - val consumedHeader = newCurrentHeaderHeight - previousHeight - - currentHeaderHeight = newCurrentHeaderHeight - - return Offset(x = 0f, y = consumedHeader / speedFactor) - } - } - } - - Column( - modifier = Modifier - .fillMaxSize() - .nestedScroll(parentScrollConnection) - .verticalScroll( - state = rememberScrollState() - ) - .background(Color(0xfff8f8f8)) - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .height(currentHeaderHeight.dp) - - ) { - // banner - Box( - modifier = Modifier - .fillMaxWidth() - .height(currentHeaderHeight.dp - userHeight.dp) - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .noRippleClickable { - Intent(Intent.ACTION_PICK).apply { - type = "image/*" - pickBannerImageLauncher.launch(this) - } - } - .shadow( - elevation = 4.dp, - shape = RoundedCornerShape( - bottomStart = 32.dp, - bottomEnd = 32.dp - ) - ) - ) { - val banner = profile?.banner - - if (banner != null) { - CustomAsyncImage( - LocalContext.current, - banner, - modifier = Modifier - .fillMaxSize(), - contentDescription = "", - contentScale = ContentScale.Crop - ) - } else { - Image( - painter = painterResource(id = R.drawable.rider_pro_moment_demo_2), - modifier = Modifier - .fillMaxSize(), - contentDescription = "", - contentScale = ContentScale.Crop - ) - } - } - if (isSelf) { - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .padding( - top = statusBarPaddingValues.calculateTopPadding(), - start = 8.dp, - end = 8.dp - ) - ) { - Box( - modifier = Modifier - .padding(16.dp) - .clip(RoundedCornerShape(8.dp)) - .shadow( - elevation = 20.dp - ) - .background(Color.White.copy(alpha = 0.7f)) - ) { - Icon( - painter = painterResource(id = R.drawable.rider_pro_more_horizon), - contentDescription = "", - modifier = Modifier.noRippleClickable { - expanded = true - }, - tint = Color.Black - ) - } - - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - width = 250, - menuItems = listOf( - MenuItem( - stringResource(R.string.logout), - R.mipmap.rider_pro_logout - ) { - expanded = false - scope.launch { - onLogout() - navController.navigate(NavigationRoute.Login.route) { - popUpTo(NavigationRoute.Index.route) { - inclusive = true - } - } - } - }, - MenuItem( - stringResource(R.string.change_password), - R.mipmap.rider_pro_change_password - ) { - expanded = false - scope.launch { - navController.navigate(NavigationRoute.ChangePasswordScreen.route) - } - }, - MenuItem( - stringResource(R.string.favourites), - R.drawable.rider_pro_favourite - ) { - expanded = false - scope.launch { - navController.navigate(NavigationRoute.FavouriteList.route) - } - } - ) - ) - } - } - - } - Box( - modifier = Modifier.fillMaxWidth() - ) { - // user info - Column( - modifier = Modifier - .fillMaxWidth() - .height(if (currentHeaderHeight.dp > bannerHeight.dp) userHeight.dp else currentHeaderHeight.dp) - ) { - Spacer(modifier = Modifier.height(16.dp)) - // 个人信息 - Box( - modifier = Modifier.padding(horizontal = 16.dp) - ) { - profile?.let { - UserItem(it) - } - } - Spacer(modifier = Modifier.height(16.dp)) - profile?.let { - Box( - modifier = Modifier.padding(horizontal = 16.dp) - ) { - if (isSelf) { - SelfProfileAction { - navController.navigate(NavigationRoute.AccountEdit.route) - } - } else { - OtherProfileAction( - it, - onFollow = { - onFollowClick() - }, - onChat = { - onChatClick() - } - ) - } - - } - } - - // collapsed bar - - - } - val thresholdHeight = 200 // 设置阈值高度 - val startChangeHeight = headerBannerMinHeight + thresholdHeight - val alpha = if (currentHeaderHeight < startChangeHeight) { - ((currentHeaderHeight - headerBannerMinHeight).toFloat() / thresholdHeight.toFloat()).coerceIn( - 0f, - 1f - ) - } else { - 1f // 高度大于阈值时,alpha 为 0 - } - Column( - modifier = Modifier - .fillMaxWidth() - // 保持在最低高度和当前高度之间 - .alpha(1 - alpha) - .background(Color(0xfff8f8f8)) - .padding(horizontal = 16.dp) - ) { - StatusBarSpacer() - - Row( - modifier = Modifier.height(headerBannerMinHeight.dp), - verticalAlignment = Alignment.CenterVertically - ) { - CustomAsyncImage( - LocalContext.current, - profile?.avatar, - modifier = Modifier - .size(48.dp) - .clip(CircleShape), - contentDescription = "", - contentScale = ContentScale.Crop - ) - Spacer(modifier = Modifier.width(16.dp)) - Text( - text = profile?.nickName ?: "", - fontSize = 16.sp, - fontWeight = FontWeight.W600, - color = Color.Black - ) - } - Spacer(modifier = Modifier.height(currentHeaderHeight.dp - headerBannerMinHeight.dp)) - } - } - - } - Spacer(modifier = Modifier.height(16.dp)) - UserContentPageIndicator(pagerState) - Spacer(modifier = Modifier.height(16.dp)) - HorizontalPager( - state = pagerState, - modifier = Modifier - .fillMaxWidth() - .weight(1f) - ) { page -> - when (page) { - 0 -> { - LazyVerticalStaggeredGrid( - columns = StaggeredGridCells.Fixed(2), - horizontalArrangement = Arrangement.spacedBy( - 8.dp - ), - verticalItemSpacing = 8.dp, - modifier = Modifier.fillMaxSize(), - state = gridScrollState, - contentPadding = PaddingValues(8.dp) - ) { - items(1) { - Box( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(0.75f) - .clip( - RoundedCornerShape(8.dp) - ) - .background(Color.White) - .padding(8.dp) - .noRippleClickable { - NewPostViewModel.asNewPost() - navController.navigate(NavigationRoute.NewPost.route) - } - ) { - Box( - modifier = Modifier - .fillMaxSize() - .clip( - RoundedCornerShape(8.dp) - ) - .background(Color(0xfff5f5f5)) - ) { - Icon( - Icons.Default.Add, - contentDescription = "", - modifier = Modifier - .size(32.dp) - .align(Alignment.Center) - ) - } - } - } - items(moments.itemCount) { idx -> - val moment = moments[idx] ?: return@items - GalleryItem(moment) - } - items(2) { - Spacer(modifier = Modifier.height(120.dp)) - } - } - } - - 1 -> { - LazyColumn( - modifier = Modifier.fillMaxSize(), - state = scrollState - ) { - if (moments.itemCount == 0) { - item { - EmptyMomentPostUnit() - } - } - - item { - for (idx in 0 until moments.itemCount) { - val moment = moments[idx] - moment?.let { - MomentPostUnit(it) - } - } - } - item { - Spacer(modifier = Modifier.height(120.dp)) - } - } - } - } - } - } - } -} - - diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/ProfileV3.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/ProfileV3.kt index 47a6580..ac2891b 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/ProfileV3.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/ProfileV3.kt @@ -32,6 +32,7 @@ import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add @@ -136,13 +137,14 @@ fun ProfileV3( scrollStrategy = ScrollStrategy.ExitUntilCollapsed, toolbarScrollable = true, enabled = enabled, - toolbar = { + toolbar = { toolbarScrollState -> Column( modifier = Modifier .fillMaxWidth() // 保持在最低高度和当前高度之间 .background(Color(0xfff8f8f8)) .padding(horizontal = 16.dp) + ) { StatusBarSpacer() @@ -177,8 +179,7 @@ fun ProfileV3( .graphicsLayer { // change alpha of Image as the toolbar expands alpha = state.toolbarState.progress - }, - + }.verticalScroll(toolbarScrollState) ) { Column( modifier = Modifier diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profilev2.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profilev2.kt deleted file mode 100644 index 37add70..0000000 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/Profilev2.kt +++ /dev/null @@ -1,476 +0,0 @@ -package com.aiosman.riderpro.ui.index.tabs.profile - -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.util.Log -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.gestures.scrollBy -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid -import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells -import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.shadow -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.paging.PagingData -import androidx.paging.compose.collectAsLazyPagingItems -import com.aiosman.riderpro.LocalNavController -import com.aiosman.riderpro.R -import com.aiosman.riderpro.entity.AccountProfileEntity -import com.aiosman.riderpro.entity.MomentEntity -import com.aiosman.riderpro.ui.NavigationRoute -import com.aiosman.riderpro.ui.composables.CustomAsyncImage -import com.aiosman.riderpro.ui.composables.DropdownMenu -import com.aiosman.riderpro.ui.composables.MenuItem -import com.aiosman.riderpro.ui.composables.pickupAndCompressLauncher -import com.aiosman.riderpro.ui.index.tabs.profile.composable.EmptyMomentPostUnit -import com.aiosman.riderpro.ui.index.tabs.profile.composable.GalleryItem -import com.aiosman.riderpro.ui.index.tabs.profile.composable.MomentPostUnit -import com.aiosman.riderpro.ui.index.tabs.profile.composable.OtherProfileAction -import com.aiosman.riderpro.ui.index.tabs.profile.composable.SelfProfileAction -import com.aiosman.riderpro.ui.index.tabs.profile.composable.UserContentPageIndicator -import com.aiosman.riderpro.ui.index.tabs.profile.composable.UserItem -import com.aiosman.riderpro.ui.modifiers.noRippleClickable -import com.aiosman.riderpro.ui.post.NewPostViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.launch -import java.io.File - - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun ProfileV2( - onUpdateBanner: ((Uri, File, Context) -> Unit)? = null, - profile: AccountProfileEntity? = null, - onLogout: () -> Unit = {}, - onFollowClick: () -> Unit = {}, - onChatClick: () -> Unit = {}, - sharedFlow: SharedFlow> = MutableStateFlow>( - PagingData.empty() - ).asStateFlow(), - isSelf: Boolean = true -) { - Box( - modifier = Modifier.background(Color(0xffFFFFFF)) - ) { - var parentScrollThreshold by remember { mutableStateOf(0) } - var remainScrollThreshold by remember { mutableStateOf(0) } - val bannerHeight = 500 - var scrollState = rememberLazyListState() - var gridScrollState = rememberLazyStaggeredGridState() - var pagerState = rememberPagerState(pageCount = { 2 }) - val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() - val context = LocalContext.current - var expanded by remember { mutableStateOf(false) } - val scope = rememberCoroutineScope() - val navController = LocalNavController.current - val moments = sharedFlow.collectAsLazyPagingItems() - val rootScrollState = rememberScrollState() - val pickBannerImageLauncher = pickupAndCompressLauncher( - context, - scope - ) { uri, file -> - onUpdateBanner?.invoke(uri, file, context) - } - val parentScrollConnection = remember { - object : NestedScrollConnection { - override fun onPreScroll( - available: Offset, - source: NestedScrollSource - ): Offset { - Log.d("ProfileV2", "onPreScroll: $available") - val delta = available.y.toInt() - if (delta < 0 && rootScrollState.value < parentScrollThreshold - remainScrollThreshold) { - val scrollAmount = minOf( - -delta, - parentScrollThreshold - remainScrollThreshold - rootScrollState.value - ) - scope.launch { - Log.d("ProfileV2", "scrollBy: $scrollAmount") - rootScrollState.scrollBy(scrollAmount.toFloat()) - } - return Offset(0f, -scrollAmount.toFloat()) - } - return Offset.Zero - } - } - } - - Column( - modifier = Modifier - .fillMaxSize() - .nestedScroll(parentScrollConnection) - .verticalScroll( - state = rootScrollState - ) - .background(Color(0xfff8f8f8)) - ) { - Box( - modifier = Modifier - .fillMaxWidth() - - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .onGloballyPositioned { - parentScrollThreshold = it.size.height - } - - ) { - // banner - Box( - modifier = Modifier - .fillMaxWidth() - .height(bannerHeight.dp) - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .noRippleClickable { - Intent(Intent.ACTION_PICK).apply { - type = "image/*" - pickBannerImageLauncher.launch(this) - } - } - .shadow( - elevation = 4.dp, - shape = RoundedCornerShape( - bottomStart = 32.dp, - bottomEnd = 32.dp - ) - ) - ) { - val banner = profile?.banner - - if (banner != null) { - CustomAsyncImage( - LocalContext.current, - banner, - modifier = Modifier - .fillMaxSize(), - contentDescription = "", - contentScale = ContentScale.Crop - ) - } else { - Image( - painter = painterResource(id = R.drawable.rider_pro_moment_demo_2), - modifier = Modifier - .fillMaxSize(), - contentDescription = "", - contentScale = ContentScale.Crop - ) - } - } - if (isSelf) { - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .padding( - top = statusBarPaddingValues.calculateTopPadding(), - start = 8.dp, - end = 8.dp - ) - ) { - Box( - modifier = Modifier - .padding(16.dp) - .clip(RoundedCornerShape(8.dp)) - .shadow( - elevation = 20.dp - ) - .background(Color.White.copy(alpha = 0.7f)) - ) { - Icon( - painter = painterResource(id = R.drawable.rider_pro_more_horizon), - contentDescription = "", - modifier = Modifier.noRippleClickable { - expanded = true - }, - tint = Color.Black - ) - } - - DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - width = 250, - menuItems = listOf( - MenuItem( - stringResource(R.string.logout), - R.mipmap.rider_pro_logout - ) { - expanded = false - scope.launch { - onLogout() - navController.navigate(NavigationRoute.Login.route) { - popUpTo(NavigationRoute.Index.route) { - inclusive = true - } - } - } - }, - MenuItem( - stringResource(R.string.change_password), - R.mipmap.rider_pro_change_password - ) { - expanded = false - scope.launch { - navController.navigate(NavigationRoute.ChangePasswordScreen.route) - } - }, - MenuItem( - stringResource(R.string.favourites), - R.drawable.rider_pro_favourite - ) { - expanded = false - scope.launch { - navController.navigate(NavigationRoute.FavouriteList.route) - } - } - ) - ) - } - } - - } - Box( - modifier = Modifier.fillMaxWidth() - ) { - // user info - Column( - modifier = Modifier - .fillMaxWidth() - ) { - Spacer(modifier = Modifier.height(16.dp)) - // 个人信息 - Box( - modifier = Modifier.padding(horizontal = 16.dp) - ) { - profile?.let { - UserItem(it) - } - } - Spacer(modifier = Modifier.height(16.dp)) - profile?.let { - Box( - modifier = Modifier.padding(horizontal = 16.dp) - ) { - if (isSelf) { - SelfProfileAction { - navController.navigate(NavigationRoute.AccountEdit.route) - } - } else { - OtherProfileAction( - it, - onFollow = { - onFollowClick() - }, - onChat = { - onChatClick() - } - ) - } - - } - } - - // collapsed bar - - - } - - } - - } - val miniBarAlpha = - if (rootScrollState.value >= parentScrollThreshold - remainScrollThreshold) { - 1f - } else { - 0f - } - Column( - modifier = Modifier - .fillMaxWidth() - .alpha(miniBarAlpha) - // 保持在最低高度和当前高度之间 - .background(Color(0xfff8f8f8)) - .padding(horizontal = 16.dp) - .onGloballyPositioned { - remainScrollThreshold = it.size.height - } - .align(Alignment.BottomCenter) - - ) { - Spacer(modifier = Modifier.height(48.dp)) - Row( - modifier = Modifier, - verticalAlignment = Alignment.CenterVertically - ) { - CustomAsyncImage( - LocalContext.current, - profile?.avatar, - modifier = Modifier - .size(48.dp) - .clip(CircleShape), - contentDescription = "", - contentScale = ContentScale.Crop - ) - Spacer(modifier = Modifier.width(16.dp)) - Text( - text = profile?.nickName ?: "", - fontSize = 16.sp, - fontWeight = FontWeight.W600, - color = Color.Black - ) - } - } - } - // 页面指示器 - Spacer(modifier = Modifier.height(16.dp)) - UserContentPageIndicator(pagerState) - Spacer(modifier = Modifier.height(16.dp)) - HorizontalPager( - state = pagerState, - modifier = Modifier - .fillMaxWidth() - .height(900.dp) - ) { page -> - when (page) { - 0 -> { - LazyVerticalStaggeredGrid( - columns = StaggeredGridCells.Fixed(2), - horizontalArrangement = Arrangement.spacedBy( - 8.dp - ), - verticalItemSpacing = 8.dp, - modifier = Modifier.fillMaxSize(), - state = gridScrollState, - contentPadding = PaddingValues(8.dp) - ) { - items(1) { - Box( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(0.75f) - .clip( - RoundedCornerShape(8.dp) - ) - .background(Color.White) - .padding(8.dp) - .noRippleClickable { - NewPostViewModel.asNewPost() - navController.navigate(NavigationRoute.NewPost.route) - } - ) { - Box( - modifier = Modifier - .fillMaxSize() - .clip( - RoundedCornerShape(8.dp) - ) - .background(Color(0xfff5f5f5)) - ) { - Icon( - Icons.Default.Add, - contentDescription = "", - modifier = Modifier - .size(32.dp) - .align(Alignment.Center) - ) - } - } - } - items(moments.itemCount) { idx -> - val moment = moments[idx] ?: return@items - GalleryItem(moment) - } - items(2) { - Spacer(modifier = Modifier.height(120.dp)) - } - } - } - - 1 -> { - LazyColumn( - modifier = Modifier.fillMaxHeight(), - state = scrollState - ) { - if (moments.itemCount == 0) { - item { - EmptyMomentPostUnit() - } - } - item { - for (idx in 0 until moments.itemCount) { - val moment = moments[idx] - moment?.let { - MomentPostUnit(it) - } - } - } - item { - Spacer(modifier = Modifier.height(120.dp)) - } - } - } - } - } - } - } -} - diff --git a/app/src/main/java/com/aiosman/riderpro/ui/login/userauth.kt b/app/src/main/java/com/aiosman/riderpro/ui/login/userauth.kt index b640a09..819c91d 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/login/userauth.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/login/userauth.kt @@ -125,7 +125,7 @@ fun UserAuthScreen() { loadLoginCaptcha() Toast.makeText( context, - "incorrect captcha,please try again", + context.getString(R.string.incorrect_captcha_please_try_again), Toast.LENGTH_SHORT ).show() } else { @@ -197,8 +197,7 @@ fun UserAuthScreen() { onLogin(captchaInfo) } } - - } + }, ) } Column( diff --git a/app/src/main/java/com/aiosman/riderpro/ui/post/NewPostViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/post/NewPostViewModel.kt index 818e5ee..960eb77 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/post/NewPostViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/post/NewPostViewModel.kt @@ -93,7 +93,7 @@ object NewPostViewModel : ViewModel() { } momentService.createMoment(textContent, 1, uploadImageList, relPostId) // 刷新个人动态 - MyProfileViewModel.loadProfile() + MyProfileViewModel.loadProfile(pullRefresh = true) MomentViewModel.refreshPager() } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/post/PostViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/post/PostViewModel.kt index 0bc4301..dd2da1d 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/post/PostViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/post/PostViewModel.kt @@ -114,6 +114,7 @@ class PostViewModel( moment?.let { userService.followUser(it.authorId.toString()) moment = moment?.copy(followStatus = true) + // 更新我的关注页面的关注数 } } @@ -121,6 +122,7 @@ class PostViewModel( moment?.let { userService.unFollowUser(it.authorId.toString()) moment = moment?.copy(followStatus = false) + // 更新我的关注页面的关注数 } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfileV2.kt b/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfileV2.kt index ef86a11..f9ac8a5 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfileV2.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfileV2.kt @@ -5,8 +5,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.lifecycle.viewmodel.compose.viewModel import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.exp.viewModelFactory -import com.aiosman.riderpro.ui.index.tabs.profile.Profile -import com.aiosman.riderpro.ui.index.tabs.profile.ProfileV2 import com.aiosman.riderpro.ui.index.tabs.profile.ProfileV3 import com.aiosman.riderpro.ui.navigateToChat diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index acb0daa..ab3dcf0 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -82,4 +82,8 @@ 重新发送 %s 用户不存在 请依次点击图片中的元素 + 验证码 + 刷新 + 清除 + 验证码错误,请重试 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index edf2438..6d1f545 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -81,4 +81,8 @@ Resend in %s user not exist Please click on the dots in the image in the correct order. + Chaptcha + Refresh + Clear + incorrect captcha,please try again \ No newline at end of file