diff --git a/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt b/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt index 8226e4a..fc9a8d2 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/AccountService.kt @@ -460,11 +460,17 @@ class AccountServiceImpl : AccountService { } override suspend fun resetPassword(email: String) { - ApiClient.api.resetPassword( + val resp = ApiClient.api.resetPassword( ResetPasswordRequestBody( username = email ) ) + if (!resp.isSuccessful) { + parseErrorResponse(resp.errorBody())?.let { + throw it.toServiceException() + } + throw ServiceException("Failed to reset password") + } } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/account/ResetPassword.kt b/app/src/main/java/com/aiosman/riderpro/ui/account/ResetPassword.kt index 82eef66..94086fa 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/account/ResetPassword.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/account/ResetPassword.kt @@ -49,8 +49,17 @@ fun ResetPasswordScreen() { var isSendSuccess by remember { mutableStateOf(null) } var isLoading by remember { mutableStateOf(false) } val navController = LocalNavController.current - + var usernameError by remember { mutableStateOf(null) } + fun validate(): Boolean { + if (username.isEmpty()) { + usernameError = context.getString(R.string.text_error_email_required) + return false + } + usernameError = null + return true + } fun resetPassword() { + if (!validate()) return scope.launch { isLoading = true try { @@ -78,7 +87,7 @@ fun ResetPasswordScreen() { ) ) { NoticeScreenHeader( - "RECOVER ACCOUNT", + stringResource(R.string.recover_account_upper), moreIcon = false ) } @@ -93,7 +102,7 @@ fun ResetPasswordScreen() { if (isSendSuccess!!) { Spacer(modifier = Modifier.height(16.dp)) Text( - text = "Reset password email has been sent to your email address", + text = stringResource(R.string.reset_mail_send_success), style = TextStyle( color = Color(0xFF333333), fontSize = 14.sp, @@ -103,7 +112,7 @@ fun ResetPasswordScreen() { } else { Spacer(modifier = Modifier.height(16.dp)) Text( - text = "Failed to send reset password email", + text = stringResource(R.string.reset_mail_send_failed), style = TextStyle( color = Color(0xFF333333), fontSize = 14.sp, @@ -138,7 +147,8 @@ fun ResetPasswordScreen() { onValueChange = { username = it }, label = stringResource(R.string.login_email_label), hint = stringResource(R.string.text_hint_email), - enabled = !isLoading + enabled = !isLoading, + error = usernameError ) Spacer(modifier = Modifier.height(72.dp)) if (isLoading) { @@ -148,7 +158,7 @@ fun ResetPasswordScreen() { modifier = Modifier .width(345.dp) .height(48.dp), - text = "Recover Account", + text = stringResource(R.string.recover), backgroundImage = R.mipmap.rider_pro_signup_red_bg ) { resetPassword() diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/FollowButton.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/FollowButton.kt new file mode 100644 index 0000000..53db789 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/FollowButton.kt @@ -0,0 +1,57 @@ +package com.aiosman.riderpro.ui.composables + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.aiosman.riderpro.R +import com.aiosman.riderpro.ui.modifiers.noRippleClickable + +@Composable +fun FollowButton( + isFollowing: Boolean, + fontSize: TextUnit = 12.sp, + imageModifier: Modifier = Modifier, + onFollowClick: () -> Unit, +){ + Box( + modifier = Modifier + .wrapContentWidth() + .padding(start = 6.dp) + .noRippleClickable { + onFollowClick() + }, + contentAlignment = Alignment.Center + ) { + Image( + modifier = imageModifier, + painter = painterResource(id = R.drawable.follow_bg), + contentDescription = "", + contentScale = ContentScale.FillWidth + ) + Text( + text = if (isFollowing) stringResource(R.string.following_upper) else stringResource( + R.string.follow_upper + ), + fontSize = fontSize, + color = Color.White, + style = TextStyle(fontWeight = FontWeight.Bold) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/TextInputField.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/TextInputField.kt index de547dc..16cb1b8 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/composables/TextInputField.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/TextInputField.kt @@ -1,5 +1,9 @@ package com.aiosman.riderpro.ui.composables +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -112,16 +116,23 @@ fun TextInputField( .height(16.dp), verticalAlignment = Alignment.CenterVertically ) { - if (error != null) { - Image( - painter = painterResource(id = R.mipmap.rider_pro_input_error), - contentDescription = "Error", - modifier = Modifier.size(8.dp) - ) - Spacer(modifier = Modifier.size(4.dp)) - Text(error, color = Color(0xFFE53935), fontSize = 12.sp) + AnimatedVisibility( + visible = error != null, + enter = fadeIn(), + exit = fadeOut() + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Image( + painter = painterResource(id = R.mipmap.rider_pro_input_error), + contentDescription = "Error", + modifier = Modifier.size(8.dp) + ) + Spacer(modifier = Modifier.size(4.dp)) + AnimatedContent(targetState = error) { targetError -> + Text(targetError ?: "", color = Color(0xFFE53935), fontSize = 12.sp) + } + } } - } } } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteNoticeScreen.kt b/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteNoticeScreen.kt index 16af645..af8367e 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteNoticeScreen.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteNoticeScreen.kt @@ -30,6 +30,7 @@ fun FavouriteNoticeScreen() { var dataFlow = model.favouriteItemsFlow var favourites = dataFlow.collectAsLazyPagingItems() LaunchedEffect(Unit) { + model.reload() model.updateNotice() } StatusBarMaskLayout( diff --git a/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteNoticeViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteNoticeViewModel.kt index 436e3df..92adfbc 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteNoticeViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/favourite/FavouriteNoticeViewModel.kt @@ -26,8 +26,13 @@ object FavouriteNoticeViewModel : ViewModel() { private val _favouriteItemsFlow = MutableStateFlow>(PagingData.empty()) val favouriteItemsFlow = _favouriteItemsFlow.asStateFlow() + var isFirstLoad = true - init { + fun reload(force: Boolean = false) { + if (!isFirstLoad && !force) { + return + } + isFirstLoad = false viewModelScope.launch { Pager( config = PagingConfig(pageSize = 5, enablePlaceholders = false), 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 7efa5c1..c2ed905 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 @@ -47,7 +47,11 @@ fun FollowerListScreen(userId: Int) { isFollowing = user.isFollowing ) { scope.launch { - model.followUser(user.id) + if (user.isFollowing) { + model.unFollowUser(user.id) + } else { + model.followUser(user.id) + } } } } 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 46f9a0a..78aab56 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 @@ -43,11 +43,11 @@ object FollowerListViewModel : ViewModel() { } } - private fun updateIsFollow(id: Int) { + private fun updateIsFollow(id: Int, isFollow: Boolean = true) { val currentPagingData = usersFlow.value val updatedPagingData = currentPagingData.map { user -> if (user.id == id) { - user.copy(isFollowing = true) + user.copy(isFollowing = isFollow) } else { user } @@ -60,4 +60,9 @@ object FollowerListViewModel : ViewModel() { updateIsFollow(userId) } + suspend fun unFollowUser(userId: Int) { + userService.unFollowUser(userId.toString()) + updateIsFollow(userId, false) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerNotice.kt b/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerNotice.kt index 988f1a5..dc433cb 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerNotice.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerNotice.kt @@ -25,12 +25,14 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.paging.compose.collectAsLazyPagingItems +import com.aiosman.riderpro.AppState import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.R import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout import com.aiosman.riderpro.ui.comment.NoticeScreenHeader import com.aiosman.riderpro.ui.composables.CustomAsyncImage +import com.aiosman.riderpro.ui.composables.FollowButton import com.aiosman.riderpro.ui.modifiers.noRippleClickable import kotlinx.coroutines.launch @@ -47,11 +49,14 @@ fun FollowerNoticeScreen() { var dataFlow = model.followerItemsFlow var followers = dataFlow.collectAsLazyPagingItems() Box( - modifier = Modifier.fillMaxWidth().padding(vertical = 16.dp) + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp) ) { NoticeScreenHeader(stringResource(R.string.followers_upper), moreIcon = false) } LaunchedEffect(Unit) { + model.reload() model.updateNotice() } LazyColumn( @@ -114,30 +119,43 @@ fun FollowItem( ) { Text(nickname, fontWeight = FontWeight.Bold, fontSize = 16.sp) } - if (!isFollowing) { - Box( - modifier = Modifier.noRippleClickable { - onFollow() - } - ) { - Image( - painter = painterResource(id = R.drawable.follow_bg), - contentDescription = "Follow", - modifier = Modifier - .width(79.dp) - .height(24.dp) - ) - Text( - "FOLLOW", - fontSize = 14.sp, - color = Color(0xFFFFFFFF), - modifier = Modifier.align( - Alignment.Center - ) - ) - } + if (userId != AppState.UserId) { + FollowButton( + isFollowing = isFollowing, + onFollowClick = onFollow, + fontSize = 14.sp, + imageModifier = Modifier + .width(100.dp) + .height(24.dp) + ) } +// Box( +// modifier = Modifier.noRippleClickable { +// onFollow() +// } +// ) { +// Image( +// painter = painterResource(id = R.drawable.follow_bg), +// contentDescription = "Follow", +// modifier = Modifier +// .width(79.dp) +// .height(24.dp) +// ) +// Text( +// text = if (isFollowing) { +// stringResource(R.string.following_upper) +// } else { +// stringResource(R.string.follow_upper) +// }, +// fontSize = 14.sp, +// color = Color(0xFFFFFFFF), +// modifier = Modifier.align( +// Alignment.Center +// ) +// ) +// } + } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerNoticeViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerNoticeViewModel.kt index ef252e9..cb72ba3 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerNoticeViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/follower/FollowerNoticeViewModel.kt @@ -30,8 +30,13 @@ object FollowerNoticeViewModel : ViewModel() { private val _followerItemsFlow = MutableStateFlow>(PagingData.empty()) val followerItemsFlow = _followerItemsFlow.asStateFlow() + var isFirstLoad = true - init { + fun reload(force: Boolean = false) { + if (!isFirstLoad && !force) { + return + } + isFirstLoad = false viewModelScope.launch { Pager( config = PagingConfig(pageSize = 5, enablePlaceholders = false), 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 e5ce050..201170c 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 @@ -18,7 +18,7 @@ import kotlinx.coroutines.launch @Composable fun FollowingListScreen(userId: Int) { - val model = FollowerListViewModel + val model = FollowingListViewModel val scope = rememberCoroutineScope() LaunchedEffect(Unit) { model.loadData(userId) @@ -47,7 +47,11 @@ fun FollowingListScreen(userId: Int) { isFollowing = user.isFollowing ) { scope.launch { - model.followUser(user.id) + if (user.isFollowing) { + model.unfollowUser(user.id) + } else { + model.followUser(user.id) + } } } } 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 6cc2f8e..fa25a5e 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 @@ -24,9 +24,6 @@ object FollowingListViewModel : ViewModel() { val usersFlow = _usersFlow.asStateFlow() private var userId by mutableStateOf(null) fun loadData(id: Int) { - if (userId == id) { - return - } userId = id viewModelScope.launch { Pager( @@ -34,7 +31,7 @@ object FollowingListViewModel : ViewModel() { pagingSourceFactory = { AccountPagingSource( userService, - followerId = id + followingId = id ) } ).flow.cachedIn(viewModelScope).collectLatest { @@ -43,11 +40,11 @@ object FollowingListViewModel : ViewModel() { } } - private fun updateIsFollow(id: Int) { + private fun updateIsFollow(id: Int, isFollow: Boolean = true) { val currentPagingData = usersFlow.value val updatedPagingData = currentPagingData.map { user -> if (user.id == id) { - user.copy(isFollowing = true) + user.copy(isFollowing = isFollow) } else { user } @@ -60,4 +57,9 @@ object FollowingListViewModel : ViewModel() { updateIsFollow(userId) } + suspend fun unfollowUser(userId: Int) { + userService.unFollowUser(userId.toString()) + updateIsFollow(userId, false) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageList.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageList.kt index c822210..43aaec1 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageList.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageList.kt @@ -54,6 +54,9 @@ import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder import com.aiosman.riderpro.ui.composables.CustomAsyncImage import com.aiosman.riderpro.ui.composables.StatusBarSpacer +import com.aiosman.riderpro.ui.favourite.FavouriteNoticeViewModel +import com.aiosman.riderpro.ui.follower.FollowerNoticeViewModel +import com.aiosman.riderpro.ui.like.LikeNoticeViewModel import com.aiosman.riderpro.ui.modifiers.noRippleClickable import com.aiosman.riderpro.ui.post.PostViewModel import com.google.accompanist.systemuicontroller.rememberSystemUiController @@ -72,7 +75,7 @@ fun NotificationsScreen() { var comments = dataFlow.collectAsLazyPagingItems() val state = rememberPullRefreshState(MessageListViewModel.isLoading, onRefresh = { MessageListViewModel.viewModelScope.launch { - MessageListViewModel.initData() + MessageListViewModel.initData(force = true) } }) LaunchedEffect(Unit) { @@ -103,6 +106,13 @@ fun NotificationsScreen() { R.drawable.rider_pro_like, stringResource(R.string.like_upper) ) { + if (MessageListViewModel.likeNoticeCount > 0) { + // 刷新点赞消息列表 + LikeNoticeViewModel.isFirstLoad = true + // 清除点赞消息数量 + MessageListViewModel.clearLikeNoticeCount() + } + navController.navigate(NavigationRoute.Likes.route) } NotificationIndicator( @@ -110,6 +120,11 @@ fun NotificationsScreen() { R.drawable.rider_pro_followers, stringResource(R.string.followers_upper) ) { + if (MessageListViewModel.followNoticeCount > 0) { + // 刷新关注消息列表 + FollowerNoticeViewModel.isFirstLoad = true + MessageListViewModel.clearFollowNoticeCount() + } navController.navigate(NavigationRoute.Followers.route) } NotificationIndicator( @@ -117,6 +132,11 @@ fun NotificationsScreen() { R.drawable.rider_pro_favoriate, stringResource(R.string.favourites_upper) ) { + if (MessageListViewModel.favouriteNoticeCount > 0) { + // 刷新收藏消息列表 + FavouriteNoticeViewModel.isFirstLoad = true + MessageListViewModel.clearFavouriteNoticeCount() + } navController.navigate(NavigationRoute.FavouritesScreen.route) } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageListViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageListViewModel.kt index c442e21..bd9fee6 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageListViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/message/MessageListViewModel.kt @@ -31,9 +31,15 @@ object MessageListViewModel : ViewModel() { private val _commentItemsFlow = MutableStateFlow>(PagingData.empty()) val commentItemsFlow = _commentItemsFlow.asStateFlow() var isLoading by mutableStateOf(false) - - suspend fun initData() { - isLoading = true + var isFirstLoad = true + suspend fun initData(force: Boolean = false) { + if (!isFirstLoad && !force) { + return + } + if (force) { + isLoading = true + } + isFirstLoad = false val info = accountService.getMyNoticeInfo() noticeInfo = info viewModelScope.launch { @@ -43,7 +49,7 @@ object MessageListViewModel : ViewModel() { CommentPagingSource( CommentRemoteDataSource(commentService), selfNotice = true, - order="latest" + order = "latest" ) } ).flow.cachedIn(viewModelScope).collectLatest { @@ -51,6 +57,7 @@ object MessageListViewModel : ViewModel() { } } isLoading = false + } val likeNoticeCount @@ -80,4 +87,15 @@ object MessageListViewModel : ViewModel() { updateIsRead(id) } } + + fun clearLikeNoticeCount() { + noticeInfo = noticeInfo?.copy(likeCount = 0) + } + + fun clearFollowNoticeCount() { + noticeInfo = noticeInfo?.copy(followCount = 0) + } + fun clearFavouriteNoticeCount() { + noticeInfo = noticeInfo?.copy(favoriteCount = 0) + } } \ No newline at end of file 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 0c633b9..1d379d6 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 @@ -37,11 +37,16 @@ object MyProfileViewModel : ViewModel() { private var _momentsFlow = MutableStateFlow>(PagingData.empty()) var momentsFlow = _momentsFlow.asStateFlow() var refreshing by mutableStateOf(false) + var firstLoad = true fun loadProfile(pullRefresh: Boolean = false) { + if (!firstLoad && !pullRefresh) { + return + } viewModelScope.launch { if (pullRefresh){ refreshing = true } + firstLoad = false profile = accountService.getMyAccountProfile() val profile = accountService.getMyAccountProfile() refreshing = false diff --git a/app/src/main/java/com/aiosman/riderpro/ui/like/LikeNoticeViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/like/LikeNoticeViewModel.kt index 6a76b56..e82bbcd 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/like/LikeNoticeViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/like/LikeNoticeViewModel.kt @@ -23,8 +23,12 @@ object LikeNoticeViewModel : ViewModel() { private val accountService: AccountService = AccountServiceImpl() private val _likeItemsFlow = MutableStateFlow>(PagingData.empty()) val likeItemsFlow = _likeItemsFlow.asStateFlow() - - init { + var isFirstLoad = true + fun reload(force: Boolean = false) { + if (!isFirstLoad && !force) { + return + } + isFirstLoad = false viewModelScope.launch { Pager( config = PagingConfig(pageSize = 5, enablePlaceholders = false), diff --git a/app/src/main/java/com/aiosman/riderpro/ui/like/LikePage.kt b/app/src/main/java/com/aiosman/riderpro/ui/like/LikePage.kt index d875630..265996f 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/like/LikePage.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/like/LikePage.kt @@ -56,8 +56,10 @@ fun LikeNoticeScreen() { var dataFlow = model.likeItemsFlow var likes = dataFlow.collectAsLazyPagingItems() LaunchedEffect(Unit) { + model.reload() model.updateNotice() } + StatusBarMaskLayout( darkIcons = true, maskBoxBackgroundColor = Color(0xFFFFFFFF) @@ -125,7 +127,7 @@ fun ActionPostNoticeItem( val context = LocalContext.current val navController = LocalNavController.current Box( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 16.dp) + modifier = Modifier.padding(vertical = 16.dp) ) { Row( modifier = Modifier.fillMaxWidth(), @@ -188,7 +190,7 @@ fun LikeCommentNoticeItem( val navController = LocalNavController.current val context = LocalContext.current Box( - modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp).noRippleClickable { + modifier = Modifier.padding(vertical = 16.dp).noRippleClickable { item.comment?.postId.let { navController.navigate( NavigationRoute.Post.route.replace( @@ -261,7 +263,8 @@ fun LikeCommentNoticeItem( Text( text = item.comment?.content ?: "", fontSize = 12.sp, - color = Color(0x99000000) + color = Color(0x99000000), + maxLines = 2 ) } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/login/emailsignup.kt b/app/src/main/java/com/aiosman/riderpro/ui/login/emailsignup.kt index 7f16145..55d8cce 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/login/emailsignup.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/login/emailsignup.kt @@ -53,9 +53,9 @@ import kotlinx.coroutines.launch @Composable fun EmailSignupScreen() { - var email by remember { mutableStateOf("takayamaaren@gmail.com") } - var password by remember { mutableStateOf("Dzh17217.") } - var confirmPassword by remember { mutableStateOf("Dzh17217.") } + var email by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + var confirmPassword by remember { mutableStateOf("") } var rememberMe by remember { mutableStateOf(false) } var acceptTerms by remember { mutableStateOf(false) } var acceptPromotions by remember { mutableStateOf(false) } @@ -68,7 +68,6 @@ fun EmailSignupScreen() { var confirmPasswordError by remember { mutableStateOf(null) } var termsError by remember { mutableStateOf(false) } var promotionsError by remember { mutableStateOf(false) } - fun validateForm(): Boolean { emailError = when { // 非空 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 6015312..5a1fb08 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 @@ -93,7 +93,13 @@ fun UserAuthScreen() { } } catch (e: ServiceException) { // handle error - Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() + + if (e.code == 12005) { + emailError = context.getString(R.string.error_invalidate_username_password) + passwordError = context.getString(R.string.error_invalidate_username_password) + } else { + Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() + } } } @@ -181,26 +187,47 @@ fun UserAuthScreen() { Row( verticalAlignment = Alignment.CenterVertically ) { - CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) { - Checkbox( - checked = rememberMe, - onCheckedChange = { - rememberMe = it - }, - colors = CheckboxDefaults.colors( - checkedColor = Color.Black - ), - ) - Text( - stringResource(R.string.remember_me), - modifier = Modifier.padding(start = 8.dp), - fontSize = 12.sp - ) - Spacer(modifier = Modifier.weight(1f)) - Text(stringResource(R.string.forgot_password), fontSize = 12.sp, modifier = Modifier.noRippleClickable { + com.aiosman.riderpro.ui.composables.Checkbox( + checked = rememberMe, + onCheckedChange = { + rememberMe = it + }, + size = 18 + ) + Text( + stringResource(R.string.remember_me), + modifier = Modifier.padding(start = 8.dp), + fontSize = 12.sp + ) + Spacer(modifier = Modifier.weight(1f)) + Text( + stringResource(R.string.forgot_password), + fontSize = 12.sp, + modifier = Modifier.noRippleClickable { navController.navigate(NavigationRoute.ResetPassword.route) - }) - } + } + ) +// CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) { +// Checkbox( +// checked = rememberMe, +// onCheckedChange = { +// rememberMe = it +// }, +// colors = CheckboxDefaults.colors( +// checkedColor = Color.Black +// ), +// ) +// Text( +// stringResource(R.string.remember_me), +// modifier = Modifier.padding(start = 8.dp), +// fontSize = 12.sp +// ) +// Spacer(modifier = Modifier.weight(1f)) +// Text(stringResource(R.string.forgot_password), fontSize = 12.sp, modifier = Modifier.noRippleClickable { +// navController.navigate(NavigationRoute.ResetPassword.route) +// }) +// } + } Spacer(modifier = Modifier.height(64.dp)) ActionButton( diff --git a/app/src/main/java/com/aiosman/riderpro/ui/post/CommentsViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/post/CommentsViewModel.kt index fd5781d..0f59d4d 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/post/CommentsViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/post/CommentsViewModel.kt @@ -29,6 +29,7 @@ class CommentsViewModel( val commentsFlow = _commentsFlow.asStateFlow() var order: String by mutableStateOf("like") var addedCommentList by mutableStateOf>(emptyList()) + var subCommentLoadingMap by mutableStateOf(mutableMapOf()) /** * 预加载,在跳转到 PostScreen 之前设置好内容 @@ -162,16 +163,23 @@ class CommentsViewModel( val currentPagingData = commentsFlow.value val updatedPagingData = currentPagingData.map { comment -> if (comment.id == commentId) { - val subCommentList = commentService.getComments( - postId = postId.toInt(), - parentCommentId = commentId, - pageNumber = comment.replyPage + 1, - pageSize = 3, - ).list - return@map comment.copy( - reply = comment.reply.plus(subCommentList), - replyPage = comment.replyPage + 1 - ) + try { + subCommentLoadingMap[commentId] = true + val subCommentList = commentService.getComments( + postId = postId.toInt(), + parentCommentId = commentId, + pageNumber = comment.replyPage + 1, + pageSize = 3, + ).list + return@map comment.copy( + reply = comment.reply.plus(subCommentList), + replyPage = comment.replyPage + 1 + ) + } catch (e: Exception) { + return@map comment.copy() + } finally { + subCommentLoadingMap[commentId] = false + } } comment } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt b/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt index f4edcee..40a0a41 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/post/Post.kt @@ -29,8 +29,9 @@ 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.text.ClickableText +import androidx.compose.material.LinearProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -47,7 +48,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext @@ -67,6 +67,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.paging.LoadState import androidx.paging.compose.collectAsLazyPagingItems import com.aiosman.riderpro.AppState import com.aiosman.riderpro.LocalAnimatedContentScope @@ -79,22 +80,18 @@ import com.aiosman.riderpro.entity.MomentImageEntity import com.aiosman.riderpro.exp.formatPostTime import com.aiosman.riderpro.exp.timeAgo import com.aiosman.riderpro.ui.NavigationRoute +import com.aiosman.riderpro.ui.comment.NoticeScreenHeader import com.aiosman.riderpro.ui.composables.AnimatedFavouriteIcon import com.aiosman.riderpro.ui.composables.AnimatedLikeIcon import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder import com.aiosman.riderpro.ui.composables.CustomAsyncImage +import com.aiosman.riderpro.ui.composables.CustomClickableText import com.aiosman.riderpro.ui.composables.EditCommentBottomModal +import com.aiosman.riderpro.ui.composables.FollowButton import com.aiosman.riderpro.ui.composables.StatusBarSpacer import com.aiosman.riderpro.ui.imageviewer.ImageViewerViewModel import com.aiosman.riderpro.ui.modifiers.noRippleClickable import kotlinx.coroutines.launch -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.material.LinearProgressIndicator -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Icon -import androidx.paging.LoadState -import com.aiosman.riderpro.ui.comment.NoticeScreenHeader -import com.aiosman.riderpro.ui.composables.CustomClickableText @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -618,31 +615,12 @@ fun Header( Spacer(modifier = Modifier.width(8.dp)) Text(text = nickname ?: "", fontWeight = FontWeight.Bold) if (AppState.UserId != userId) { - Box( - modifier = Modifier - .height(20.dp) - .wrapContentWidth() - .padding(start = 6.dp) - .noRippleClickable { - onFollowClick() - }, - contentAlignment = Alignment.Center - ) { - Image( - modifier = Modifier.height(18.dp).width(80.dp), - painter = painterResource(id = R.drawable.follow_bg), - contentDescription = "", - contentScale = ContentScale.FillWidth - ) - Text( - text = if (isFollowing) stringResource(R.string.following_upper) else stringResource( - R.string.follow_upper - ), - fontSize = 12.sp, - color = Color.White, - style = TextStyle(fontWeight = FontWeight.Bold) - ) - } + FollowButton( + isFollowing = isFollowing, + onFollowClick = onFollowClick, + imageModifier = Modifier.height(18.dp).width(80.dp), + fontSize = 12.sp + ) } if (AppState.UserId == userId) { Spacer(modifier = Modifier.weight(1f)) @@ -757,28 +735,6 @@ fun PostDetails( } } -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun LongPressClickableText( - text: AnnotatedString, - onClick: (Int) -> Unit, - onLongClick: () -> Unit, - style: TextStyle = TextStyle.Default -) { - ClickableText( - text = text, - onClick = onClick, - style = style, - modifier = Modifier.pointerInput(Unit) { - detectTapGestures( - onLongPress = { - onLongClick() - }, - ) - } - ) -} - @OptIn(ExperimentalFoundationApi::class) @Composable fun CommentItem( @@ -910,7 +866,7 @@ fun CommentItem( Spacer(modifier = Modifier.width(8.dp)) if (AppState.UserId?.toLong() != commentEntity.author) { Text( - text = "Reply", + text = stringResource(R.string.reply), fontSize = 12.sp, color = Color.Gray, modifier = Modifier.noRippleClickable { @@ -970,7 +926,7 @@ fun CommentItem( if (commentEntity.replyCount > 0 && !isChild && commentEntity.reply.size < commentEntity.replyCount) { val remaining = commentEntity.replyCount - commentEntity.reply.size Text( - text = "View $remaining more replies", + text = stringResource(R.string.view_more_reply, remaining), fontSize = 12.sp, color = Color(0xFF6F94AE), modifier = Modifier.noRippleClickable { @@ -1242,7 +1198,7 @@ fun CommentMenuModal( commentEntity?.let { Spacer(modifier = Modifier.width(48.dp)) MenuActionItem( - text = "Like", + text = stringResource(R.string.like), content = { AnimatedLikeIcon( liked = it.liked, @@ -1258,7 +1214,7 @@ fun CommentMenuModal( Spacer(modifier = Modifier.width(48.dp)) MenuActionItem( icon = R.drawable.rider_pro_comment, - text = "Reply" + text = stringResource(R.string.reply) ) { onReplyClick() } diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 2619e37..8de99af 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -61,4 +61,13 @@ 原始图片 收藏 删除 + 复制 + 点赞 + 回复 + 查看更多%1d条回复 + 错误的用户名或密码 + 找回密码 + 找回 + 邮件已发送!请查收您的邮箱,按照邮件中的指示重置密码。 + 邮件发送失败,请检查您的网络连接或稍后重试。 \ 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 c666a81..29f2fe3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -61,4 +61,12 @@ Favourite Delete Copy + Like + Reply + View %1d more replies + Invalid email or password + RCOVER ACCOUNT + Recover + An email has been sent to your registered email address. Please check your inbox and follow the instructions to reset your password. + Failed to send email. Please check your network connection or try again later. \ No newline at end of file