diff --git a/app/src/main/java/com/aiosman/riderpro/data/DictService.kt b/app/src/main/java/com/aiosman/riderpro/data/DictService.kt new file mode 100644 index 0000000..6663ca6 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/data/DictService.kt @@ -0,0 +1,18 @@ +package com.aiosman.riderpro.data + +import com.aiosman.riderpro.data.api.ApiClient +import com.aiosman.riderpro.data.api.DictItem + +interface DictService { + /** + * 获取字典项 + */ + suspend fun getDictByKey(key: String): DictItem +} + +class DictServiceImpl : DictService { + override suspend fun getDictByKey(key: String): DictItem { + val resp = ApiClient.api.getDict(key) + return resp.body()?.data ?: throw Exception("failed to get dict") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt b/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt index f2d1209..a63e7bc 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/api/RiderProAPI.kt @@ -119,6 +119,15 @@ data class AppConfig( val trtcAppId: Int, ) +data class DictItem( + @SerializedName("key") + val key: String, + @SerializedName("value") + val value: String, + @SerializedName("desc") + val desc: String, +) + interface RiderProAPI { @POST("register") suspend fun register(@Body body: RegisterRequestBody): Response @@ -322,4 +331,9 @@ interface RiderProAPI { @GET("app/info") suspend fun getAppConfig(): Response> + + @GET("dict") + suspend fun getDict( + @Query("key") key: String + ): Response> } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt index 71ef5bc..5fa40c8 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt @@ -53,7 +53,7 @@ import com.aiosman.riderpro.ui.modification.EditModificationScreen import com.aiosman.riderpro.ui.post.NewPostImageGridScreen import com.aiosman.riderpro.ui.post.NewPostScreen import com.aiosman.riderpro.ui.post.PostScreen -import com.aiosman.riderpro.ui.profile.AccountProfile +import com.aiosman.riderpro.ui.profile.AccountProfileV2 sealed class NavigationRoute( val route: String, @@ -240,7 +240,7 @@ fun NavigationController( CompositionLocalProvider( LocalAnimatedContentScope provides this, ) { - AccountProfile(it.arguments?.getString("id")!!) + AccountProfileV2(it.arguments?.getString("id")!!) } } composable( @@ -363,7 +363,6 @@ fun NavigationController( } } - } @OptIn(ExperimentalSharedTransitionApi::class) @@ -394,7 +393,7 @@ fun Navigation( fun NavHostController.navigateToPost( id: Int, highlightCommentId: Int? = 0, - initImagePagerIndex: Int? = null + initImagePagerIndex: Int? = 0 ) { navigate( route = NavigationRoute.Post.route diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/Image.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/Image.kt index bc8bfe2..cbc1ba6 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/composables/Image.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/Image.kt @@ -3,7 +3,6 @@ package com.aiosman.riderpro.ui.composables import android.content.Context import android.graphics.Bitmap import androidx.annotation.DrawableRes -import androidx.compose.foundation.Image import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -13,15 +12,14 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp import androidx.core.graphics.drawable.toBitmap -import androidx.core.graphics.drawable.toDrawable import coil.ImageLoader import coil.compose.AsyncImage import coil.compose.rememberAsyncImagePainter import coil.compose.rememberImagePainter import coil.request.ImageRequest import coil.request.SuccessResult -import com.aiosman.riderpro.utils.BlurHashDecoder import com.aiosman.riderpro.utils.Utils.getImageLoader import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/app/src/main/java/com/aiosman/riderpro/ui/composables/PolicyCheckbox.kt b/app/src/main/java/com/aiosman/riderpro/ui/composables/PolicyCheckbox.kt new file mode 100644 index 0000000..a089a03 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/composables/PolicyCheckbox.kt @@ -0,0 +1,144 @@ +package com.aiosman.riderpro.ui.composables + +import android.net.http.SslError +import android.webkit.SslErrorHandler +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +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.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import com.aiosman.riderpro.data.DictService +import com.aiosman.riderpro.data.DictServiceImpl +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) +@Composable +fun PolicyCheckbox( + checked: Boolean = false, + error: Boolean = false, + onCheckedChange: (Boolean) -> Unit, +) { + var showModal by remember { mutableStateOf(false) } + var modalSheetState = androidx.compose.material3.rememberModalBottomSheetState( + skipPartiallyExpanded = true, + ) + var scope = rememberCoroutineScope() + val dictService: DictService = DictServiceImpl() + var policyUrl by remember { mutableStateOf("") } + fun openPolicyModel() { + scope.launch { + try { + val resp = dictService.getDictByKey("private_policy") + policyUrl = resp.value + showModal = true + + } catch (e: Exception) { + e.printStackTrace() + } + } + + } + if (showModal) { + ModalBottomSheet( + onDismissRequest = { + showModal = false + }, + sheetState = modalSheetState, + windowInsets = WindowInsets(0), + containerColor = Color.White, + ) { + WebViewDisplay( + url = policyUrl + ) + } + } + Row { + Checkbox( + checked = checked, + onCheckedChange = { + onCheckedChange(it) + }, + size = 16 + ) + val text = buildAnnotatedString { + append("I agree to the ") + withStyle(style = SpanStyle(color = if (error) Color.Red else Color.Black)) { + append("terms and conditions") + } + addStyle( + style = SpanStyle( + color = Color.Blue, + textDecoration = TextDecoration.Underline + ), + start = "I agree to the ".length, + end = "I agree to the terms and conditions".length + ) + } + ClickableText( + text = text, + modifier = Modifier.padding(start = 8.dp), + onClick = { + openPolicyModel() + }, + style = TextStyle( + fontSize = 12.sp, + color = if (error) Color.Red else Color.Black + ) + ) + } +} + +@Composable +fun WebViewDisplay(modifier: Modifier = Modifier, url: String) { + LazyColumn( + modifier = modifier.fillMaxSize() + ) { + item { + AndroidView( + factory = { context -> + WebView(context).apply { + webViewClient = object : WebViewClient() { + override fun onReceivedSslError( + view: WebView?, + handler: SslErrorHandler?, + error: SslError? + ) { + handler?.proceed() // 忽略证书错误 + } + } + settings.apply { + domStorageEnabled = true + mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW + } + loadUrl(url) + } + }, + modifier = modifier.fillMaxSize() + ) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt index 0dd154c..8b93c90 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/index/Index.kt @@ -33,9 +33,7 @@ import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.index.tabs.add.AddPage import com.aiosman.riderpro.ui.index.tabs.message.NotificationsScreen import com.aiosman.riderpro.ui.index.tabs.moment.MomentsList -import com.aiosman.riderpro.ui.index.tabs.profile.v2.Profile2 -import com.aiosman.riderpro.ui.index.tabs.profile.v2.Profile4 -import com.aiosman.riderpro.ui.index.tabs.profile.v2.ProfilePage +import com.aiosman.riderpro.ui.index.tabs.profile.ProfileWrap import com.aiosman.riderpro.ui.index.tabs.search.DiscoverScreen import com.aiosman.riderpro.ui.index.tabs.shorts.ShortVideo import com.aiosman.riderpro.ui.index.tabs.street.StreetPage @@ -209,7 +207,7 @@ fun Profile() { verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { - Profile2() + ProfileWrap() } } 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 1ceaaff..c78ecdc 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 @@ -17,7 +17,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState @@ -27,7 +26,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -37,21 +35,14 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewModelScope -import androidx.paging.LoadState -import androidx.paging.compose.collectAsLazyPagingItems import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.R -import com.aiosman.riderpro.entity.CommentEntity -import com.aiosman.riderpro.exp.timeAgo import com.aiosman.riderpro.ui.NavigationRoute 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.navigateToChat -import com.aiosman.riderpro.ui.navigateToPost import com.google.accompanist.systemuicontroller.rememberSystemUiController import kotlinx.coroutines.launch 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 552bf7e..6aabe29 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 @@ -5,7 +5,6 @@ import android.net.Uri import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -19,7 +18,6 @@ import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.AccountServiceImpl import com.aiosman.riderpro.data.MomentService import com.aiosman.riderpro.data.UploadImage -import com.aiosman.riderpro.data.UserServiceImpl import com.aiosman.riderpro.entity.AccountProfileEntity import com.aiosman.riderpro.entity.MomentEntity import com.aiosman.riderpro.entity.MomentPagingSource @@ -35,23 +33,24 @@ object MyProfileViewModel : ViewModel() { val accountService: AccountService = AccountServiceImpl() val momentService: MomentService = MomentServiceImpl() var profile by mutableStateOf(null) - private var _momentsFlow = MutableStateFlow>(PagingData.empty()) - var momentsFlow = _momentsFlow.asStateFlow() + private var _sharedFlow = MutableStateFlow>(PagingData.empty()) + var sharedFlow = _sharedFlow.asStateFlow() + var refreshing by mutableStateOf(false) var firstLoad = true + fun loadProfile(pullRefresh: Boolean = false) { -// if (!firstLoad && !pullRefresh) { -// return -// } + if (!firstLoad) return viewModelScope.launch { - if (pullRefresh){ + if (pullRefresh) { refreshing = true } firstLoad = false val profile = accountService.getMyAccountProfile() - this@MyProfileViewModel.profile = profile + MyProfileViewModel.profile = profile refreshing = false try { + // Collect shared flow Pager( config = PagingConfig(pageSize = 5, enablePlaceholders = false), pagingSourceFactory = { @@ -61,13 +60,11 @@ object MyProfileViewModel : ViewModel() { ) } ).flow.cachedIn(viewModelScope).collectLatest { - _momentsFlow.value = it + _sharedFlow.value = it } - }catch (e: Exception){ + } catch (e: Exception) { Log.e("MyProfileViewModel", "loadProfile: ", e) } - - } } @@ -76,7 +73,6 @@ object MyProfileViewModel : ViewModel() { token = null rememberMe = false saveData() - } AppState.ReloadAppState() } @@ -115,8 +111,7 @@ object MyProfileViewModel : ViewModel() { fun ResetModel() { profile = null - _momentsFlow.value = PagingData.empty() + _sharedFlow.value = PagingData.empty() firstLoad = true } - } \ No newline at end of file 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 index 4e1366d..a6cfed5 100644 --- 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 @@ -1,45 +1,48 @@ 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.annotation.DrawableRes import androidx.compose.foundation.Canvas +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.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow +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.navigationBars 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.layout.widthIn 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.material.Button -import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material.pullrefresh.PullRefreshIndicator -import androidx.compose.material.pullrefresh.pullRefresh -import androidx.compose.material.pullrefresh.rememberPullRefreshState -import androidx.compose.material3.DropdownMenu +import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -47,6 +50,7 @@ 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 @@ -55,6 +59,9 @@ import androidx.compose.ui.draw.shadow import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.PathEffect +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 @@ -62,14 +69,13 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.navigation.NavController +import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData import androidx.paging.compose.collectAsLazyPagingItems -import com.aiosman.riderpro.AppState import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.R import com.aiosman.riderpro.entity.AccountProfileEntity @@ -77,75 +83,163 @@ import com.aiosman.riderpro.entity.MomentEntity import com.aiosman.riderpro.exp.formatPostTime2 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.modifiers.noRippleClickable -import com.aiosman.riderpro.ui.navigateToChat import com.aiosman.riderpro.ui.navigateToPost import com.aiosman.riderpro.ui.post.NewPostViewModel -import com.aiosman.riderpro.ui.post.PostViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch - -@OptIn(ExperimentalMaterialApi::class) @Composable -fun ProfilePage() { - val model = MyProfileViewModel - var expanded by remember { mutableStateOf(false) } +fun ProfileWrap( + +) { LaunchedEffect(Unit) { - model.loadProfile() + MyProfileViewModel.loadProfile() } - val moments = model.momentsFlow.collectAsLazyPagingItems() - val navController: NavController = LocalNavController.current - val scope = rememberCoroutineScope() - val state = rememberPullRefreshState(model.refreshing, onRefresh = { - model.loadProfile(pullRefresh = true) - }) - val context = LocalContext.current - val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() - val pickBannerImageLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.StartActivityForResult() - ) { result -> - if (result.resultCode == Activity.RESULT_OK) { - val uri = result.data?.data - uri?.let { - model.updateUserProfileBanner(it, context = context) + Profile( + onUpdateBanner = { uri, context -> + MyProfileViewModel.updateUserProfileBanner(uri, context) + }, + onLogout = { + MyProfileViewModel.viewModelScope.launch { + MyProfileViewModel.logout() + } + + }, + profile = MyProfileViewModel.profile, + sharedFlow = MyProfileViewModel.sharedFlow + ) +} + +@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.5f + 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) + } } } - } - Box( - modifier = Modifier - .fillMaxSize() - .background(Color(0xFFF5F5F5)) - .padding(bottom = with(LocalDensity.current) { - val da = WindowInsets.navigationBars - .getBottom(this) - .toDp() + 48.dp - da - }) - .pullRefresh(state) - ) { + 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 + } + } - LazyColumn( - modifier = Modifier.fillMaxSize() + // 计算新的高度 + 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) + } + + override suspend fun onPreFling(available: Velocity): Velocity { + return Velocity.Zero + } + + override suspend fun onPostFling( + consumed: Velocity, + available: Velocity + ): Velocity { + return Velocity.Zero + } + } + } + + Column( + modifier = Modifier + .fillMaxSize() + .nestedScroll(parentScrollConnection) + .verticalScroll( + state = rememberScrollState() + ) + .background(Color(0xfff8f8f8)) ) { - item { + Column( + modifier = Modifier + .fillMaxWidth() + .height(currentHeaderHeight.dp) + + ) { + // banner Box( modifier = Modifier .fillMaxWidth() - + .height(currentHeaderHeight.dp - userHeight.dp) ) { Box( modifier = Modifier .fillMaxWidth() - .height(400.dp) .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 = model.profile?.banner + val banner = profile?.banner if (banner != null) { CustomAsyncImage( @@ -166,482 +260,518 @@ fun ProfilePage() { ) } } - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .padding( - top = statusBarPaddingValues.calculateTopPadding(), - start = 8.dp, - end = 8.dp - ) - ) { + if (isSelf) { Box( modifier = Modifier - .padding(16.dp) - .clip(RoundedCornerShape(8.dp)) - .shadow( - elevation = 20.dp + .align(Alignment.TopEnd) + .padding( + top = statusBarPaddingValues.calculateTopPadding(), + start = 8.dp, + end = 8.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 - ) - } + 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 + ) + } - com.aiosman.riderpro.ui.composables.DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - width = 250, - menuItems = listOf( - MenuItem( - stringResource(R.string.logout), - R.mipmap.rider_pro_logout - ) { - expanded = false - scope.launch { - model.logout() - navController.navigate(NavigationRoute.Login.route) { - popUpTo(NavigationRoute.Index.route) { - inclusive = true + 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) + } } - }, - MenuItem( - stringResource(R.string.change_password), - R.mipmap.rider_pro_change_password - ) { - expanded = false - scope.launch { - navController.navigate(NavigationRoute.ChangePasswordScreen.route) + ) + ) + } + } + + } + // user info + Column( + modifier = Modifier + .fillMaxWidth() + .height(if (currentHeaderHeight.dp > bannerHeight.dp) userHeight.dp else currentHeaderHeight.dp) + ) { + if (currentHeaderHeight.dp > headerBannerMinHeight.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) } - }, - MenuItem( - stringResource(R.string.favourites), - R.drawable.rider_pro_favourite + } else { + OtherProfileAction( + it, + onFollow = { + onFollowClick() + }, + onChat = { + onChatClick() + } + ) + } + + } + } + } else { + StatusBarSpacer() + Row( + modifier = Modifier + .fillMaxWidth() + .height(headerBannerMinHeight.dp) +// .background(Color(0xfff8f8f8)) + .padding(horizontal = 16.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(16.dp)) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .clip(RoundedCornerShape(32.dp)) + .background(if (pagerState.currentPage == 0) Color(0xFFFFFFFF) else Color.Transparent) + .padding(horizontal = 16.dp, vertical = 4.dp) + .noRippleClickable { + // switch to gallery + scope.launch { + pagerState.scrollToPage(0) + } + } + ) { + Text( + text = "Gallery", + fontSize = 14.sp, + fontWeight = FontWeight.W600, + color = Color.Black, + modifier = Modifier.padding(8.dp) + ) + } + Spacer(modifier = Modifier.width(4.dp)) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .clip(RoundedCornerShape(32.dp)) + .background(if (pagerState.currentPage == 1) Color(0xFFFFFFFF) else Color.Transparent) + .padding(horizontal = 16.dp, vertical = 8.dp) + .noRippleClickable { + // switch to moments + scope.launch { + pagerState.scrollToPage(1) + } + } + ) { + Text( + text = "Moments", + fontSize = 14.sp, + fontWeight = FontWeight.W600, + color = Color.Black, + modifier = Modifier.padding(8.dp) + ) + } + } + 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) + } ) { - expanded = false - scope.launch { - navController.navigate(NavigationRoute.FavouriteList.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 + Box( + modifier = Modifier + .fillMaxWidth() + .height(300.dp) + .clip(RoundedCornerShape(8.dp)) + .noRippleClickable { + navController.navigateToPost( + moment.id + ) + } - ) + + ) { + CustomAsyncImage( + LocalContext.current, + moment.images[0].thumbnail, + modifier = Modifier + .fillMaxSize(), + contentDescription = "", + contentScale = ContentScale.Crop, + ) + } + } + items(2) { + Spacer(modifier = Modifier.height(120.dp)) + } + } + } + + 1 -> { + LazyColumn( + modifier = Modifier.fillMaxHeight(), + state = scrollState + ) { + item { + if (moments.itemCount == 0) { + EmptyMomentPostUnit() + } + } + item { + for (idx in 0 until moments.itemCount) { + val moment = moments[idx] + moment?.let { + MomentPostUnit(it) + } + } + } + item { + Spacer(modifier = Modifier.height(120.dp)) + } + } } } - Spacer(modifier = Modifier.height(32.dp)) - model.profile?.let { - UserInformation( - accountProfileEntity = it, - onEditProfileClick = { - navController.navigate(NavigationRoute.AccountEdit.route) - } - ) - } - if (moments.itemCount == 0) { - EmptyMomentPostUnit() - } } - - items(moments.itemCount) { idx -> - val momentItem = moments[idx] ?: return@items - MomentPostUnit(momentItem) - } - item { - Spacer(modifier = Modifier.height(48.dp)) - } - - } - PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter)) - } - - -} - -@Composable -fun CarGroup() { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(top = 54.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - CarTopInformation() - CarTopPicture() } } @Composable -fun CarTopInformation() { - Row { - Text( - text = "BMW", - color = Color.Black, - fontSize = 12.sp, - style = TextStyle(fontWeight = FontWeight.Bold) - ) - Text( - modifier = Modifier.padding(start = 4.dp), - text = "/", - color = Color.Gray, - fontSize = 12.sp - ) - Text( - modifier = Modifier.padding(start = 4.dp), - text = "M1000RR", - color = Color.Gray, - fontSize = 12.sp - ) - } -} - -@Composable -fun CarTopPicture() { - Image( - modifier = Modifier - .size(width = 336.dp, height = 224.dp) - .padding(top = 42.dp), - painter = painterResource(id = R.drawable.default_profile_moto), contentDescription = "" - ) -} - -@Composable -fun UserInformation( - isSelf: Boolean = true, - accountProfileEntity: AccountProfileEntity, - onFollowClick: () -> Unit = {}, - onEditProfileClick: () -> Unit = {} -) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp, start = 33.dp, end = 33.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Row(modifier = Modifier.fillMaxWidth()) { - val userInfoModifier = Modifier.weight(1f) - UserInformationFollowers(userInfoModifier, accountProfileEntity) - UserInformationBasic(userInfoModifier, accountProfileEntity) - UserInformationFollowing(userInfoModifier, accountProfileEntity) - } - UserInformationSlogan(accountProfileEntity) - CommunicationOperatorGroup( - isSelf = isSelf, - isFollowing = accountProfileEntity.isFollowing, - onFollowClick = onFollowClick, - onEditProfileClick = onEditProfileClick, - accountProfileEntity = accountProfileEntity - ) - } -} - -@Composable -fun UserInformationFollowers(modifier: Modifier, accountProfileEntity: AccountProfileEntity) { +fun UserItem(accountProfileEntity: AccountProfileEntity) { val navController = LocalNavController.current - Column(modifier = modifier.padding(top = 31.dp)) { - Text( - modifier = Modifier - .padding(bottom = 5.dp) - .noRippleClickable { - navController.navigate( - NavigationRoute.FollowerList.route.replace( - "{id}", - accountProfileEntity.id.toString() - ) - ) - }, - text = accountProfileEntity.followerCount.toString(), - fontSize = 24.sp, - color = Color.Black, - style = TextStyle(fontStyle = FontStyle.Italic, fontWeight = FontWeight.Bold) - ) - Canvas( - modifier = Modifier - .size(width = 88.83.dp, height = 1.dp) - .padding(top = 2.dp, bottom = 5.dp) - ) { - drawLine( - color = Color(0xFFCCCCCC), - start = Offset(0f, 0f), - end = Offset(size.width, 0f), - strokeWidth = 1f, - pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f) - ) - } - Text( - modifier = Modifier.padding(top = 5.dp), - text = stringResource(R.string.followers_upper), - fontSize = 12.sp, - color = Color.Black, - style = TextStyle(fontWeight = FontWeight.Bold) - ) - } -} - -@Composable -fun UserInformationBasic(modifier: Modifier, accountProfileEntity: AccountProfileEntity) { - val context = LocalContext.current Column( - horizontalAlignment = Alignment.CenterHorizontally + modifier = Modifier + .fillMaxWidth() ) { - Box( - modifier = Modifier.size(width = 112.dp, height = 112.dp), - contentAlignment = Alignment.Center + Row( + verticalAlignment = Alignment.CenterVertically ) { - Image( - modifier = Modifier.fillMaxSize(), - painter = painterResource(id = R.drawable.avatar_bold), contentDescription = "" - ) + // 头像 CustomAsyncImage( - context, + LocalContext.current, accountProfileEntity.avatar, modifier = Modifier - .size(width = 88.dp, height = 88.dp) - .clip( - RoundedCornerShape(88.dp) - ), + .clip(CircleShape) + .size(48.dp), contentDescription = "", contentScale = ContentScale.Crop ) + Spacer(modifier = Modifier.width(32.dp)) + //个人统计 + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.weight(1f) + ) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .weight(1f) + .noRippleClickable { + navController.navigate( + NavigationRoute.FollowerList.route.replace( + "{id}", + accountProfileEntity.id.toString() + ) + ) + } + ) { + Text( + text = accountProfileEntity.followerCount.toString(), + fontWeight = FontWeight.W600, + fontSize = 16.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.followers_upper), + ) + } + Column( + verticalArrangement = Arrangement.Center, + modifier = Modifier + .weight(1f) + .noRippleClickable { + navController.navigate( + NavigationRoute.FollowingList.route.replace( + "{id}", + accountProfileEntity.id.toString() + ) + ) + }, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + text = accountProfileEntity.followingCount.toString(), + fontWeight = FontWeight.W600, + fontSize = 16.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = stringResource(R.string.following_upper), + ) + } + Column( + verticalArrangement = Arrangement.Center, + modifier = Modifier.weight(1f), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + } + } } + Spacer(modifier = Modifier.height(12.dp)) + // 昵称 Text( - modifier = Modifier - .widthIn(max = 220.dp) - .padding(top = 8.dp), text = accountProfileEntity.nickName, - fontSize = 32.sp, - color = Color.Black, - style = TextStyle(fontWeight = FontWeight.Bold), - textAlign = TextAlign.Center + fontWeight = FontWeight.W600, + fontSize = 16.sp, ) + Spacer(modifier = Modifier.height(4.dp)) + // 个人简介 Text( - modifier = Modifier.padding(top = 4.dp), - text = accountProfileEntity.country, - fontSize = 12.sp, + text = accountProfileEntity.bio, + fontSize = 14.sp, color = Color.Gray ) + } + } @Composable -fun UserInformationFollowing(modifier: Modifier, accountProfileEntity: AccountProfileEntity) { - val navController = LocalNavController.current - Column( - modifier = modifier.padding(top = 6.dp), - horizontalAlignment = Alignment.End - ) { - Text( - modifier = Modifier - .padding(bottom = 5.dp) - .noRippleClickable { - navController.navigate( - NavigationRoute.FollowingList.route.replace( - "{id}", - accountProfileEntity.id.toString() - ) - ) - }, - text = accountProfileEntity.followingCount.toString(), - fontSize = 24.sp, - color = Color.Black, - style = TextStyle(fontStyle = FontStyle.Italic, fontWeight = FontWeight.Bold) - ) - Canvas( - modifier = Modifier - .size(width = 88.83.dp, height = 1.dp) - .padding(top = 2.dp, bottom = 5.dp) - ) { - drawLine( - color = Color(0xFFCCCCCC), - start = Offset(0f, 0f), - end = Offset(size.width, 0f), - strokeWidth = 1f, - pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f) - ) - } - Text( - modifier = Modifier.padding(top = 5.dp), - text = stringResource(R.string.following_upper), - fontSize = 12.sp, - color = Color.Black, - style = TextStyle(fontWeight = FontWeight.Bold) - ) - } -} - -@Composable -fun UserInformationSlogan(accountProfileEntity: AccountProfileEntity) { - Text( - modifier = Modifier.padding(top = 23.dp), - text = accountProfileEntity.bio, - fontSize = 13.sp, - color = Color.Black, - style = TextStyle(fontWeight = FontWeight.Bold) - ) -} - -@Composable -fun CommunicationOperatorGroup( - accountProfileEntity: AccountProfileEntity, - isSelf: Boolean = true, - isFollowing: Boolean = false, - onFollowClick: () -> Unit = {}, - onEditProfileClick: () -> Unit = {} +fun SelfProfileAction( + onEditProfile: () -> Unit ) { - val navController = LocalNavController.current + // 按钮 Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, modifier = Modifier - .fillMaxWidth() - .padding(top = 16.dp), horizontalArrangement = Arrangement.Center - ) { - if (!isSelf && AppState.UserId != accountProfileEntity.id) { - Box( - modifier = Modifier - .size(width = 142.dp, height = 40.dp) - .noRippleClickable { - onFollowClick() - }, - contentAlignment = Alignment.Center - ) { - Image( - modifier = Modifier.fillMaxSize(), - painter = if (isFollowing) painterResource(id = R.mipmap.rider_pro_follow_grey) else painterResource( - id = R.mipmap.rider_pro_follow_red - ), - contentDescription = "" - ) - Text( - text = if (isFollowing) stringResource(R.string.following_upper) else stringResource( - R.string.follow_upper - ), - fontSize = 14.sp, - color = if (isFollowing) Color.Black else Color.White, - style = TextStyle(fontWeight = FontWeight.W600, fontStyle = FontStyle.Italic), - ) - } - Spacer(modifier = Modifier.width(16.dp)) - Box( - modifier = Modifier - .size(width = 142.dp, height = 40.dp) - .noRippleClickable { - navController.navigateToChat(accountProfileEntity.id.toString()) - }, - contentAlignment = Alignment.Center - ) { - Image( - modifier = Modifier.fillMaxSize(), - painter = painterResource(id = R.mipmap.rider_pro_btn_bg_grey), - contentDescription = "" - ) - Text( - text = "CHAT", - fontSize = 14.sp, - color = Color.Black, - fontWeight = FontWeight.Bold, - style = TextStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic) - ) - } - } - - if (isSelf) { - Box( - modifier = Modifier - .size(width = 142.dp, height = 40.dp) - .noRippleClickable { - onEditProfileClick() - }, - contentAlignment = Alignment.Center - ) { - Image( - modifier = Modifier.fillMaxSize(), - painter = painterResource(id = R.mipmap.rider_pro_btn_bg_grey), - contentDescription = "" - ) - Text( - text = stringResource(R.string.edit_profile), - fontSize = 14.sp, - color = Color.Black, - fontWeight = FontWeight.Bold, - style = TextStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic) - ) - } - } - } -} - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun RidingStyle() { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(start = 24.dp, top = 40.dp, end = 24.dp), - horizontalAlignment = Alignment.Start + .clip(RoundedCornerShape(32.dp)) + .background(Color(0xffebebeb)) + .padding(horizontal = 16.dp, vertical = 4.dp) + .noRippleClickable { + onEditProfile() + } ) { + Icon( + Icons.Default.Edit, + contentDescription = "", + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) Text( - text = "RIDING STYLES", - fontSize = 18.sp, + text = stringResource(R.string.edit_profile), + fontSize = 14.sp, + fontWeight = FontWeight.W600, color = Color.Black, - style = TextStyle(fontWeight = FontWeight.Bold) + modifier = Modifier.padding(8.dp) ) - Image( - modifier = Modifier - .padding(top = 4.dp) - .height(8.dp), - painter = painterResource(id = R.drawable.rider_pro_profile_line), - contentDescription = "" - ) - FlowRow( - modifier = Modifier - .fillMaxWidth() - .padding(top = 24.dp) - ) { - RidingStyleItem(styleContent = "Cruiser") - RidingStyleItem(styleContent = "Bobber") - RidingStyleItem(styleContent = "Cafe") - RidingStyleItem(styleContent = "Chopper") - RidingStyleItem(styleContent = "Sport") - RidingStyleItem(styleContent = "Vintage") - RidingStyleItem(styleContent = "Trike") - RidingStyleItem(styleContent = "Touring") - } + } } @Composable -fun RidingStyleItem(styleContent: String) { - Box( - modifier = Modifier.padding(bottom = 8.dp, end = 8.dp), - contentAlignment = Alignment.Center +fun OtherProfileAction( + profile: AccountProfileEntity, + onFollow: (() -> Unit)? = null, + onChat: (() -> Unit)? = null +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, ) { - Image( - modifier = Modifier.shadow( - ambientColor = Color.Gray, - spotColor = Color(0f, 0f, 0f, 0.2f), - elevation = 20.dp, - ), - painter = painterResource(id = R.drawable.rider_pro_style_wrapper), - contentDescription = "" - ) - Text( - modifier = Modifier.padding(start = 5.dp, end = 5.dp), - text = styleContent, - fontSize = 12.sp, - color = Color.Gray, - style = TextStyle(fontWeight = FontWeight.Bold) - ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .clip(RoundedCornerShape(32.dp)) + .background(if (profile.isFollowing) Color(0xffebebeb) else Color(0xffda3833)) + .padding(horizontal = 16.dp, vertical = 4.dp) + .noRippleClickable { + onFollow?.invoke() + } + ) { + Icon( + Icons.Default.Add, + contentDescription = "", + modifier = Modifier.size(24.dp), + tint = if (profile.isFollowing) Color.Black else Color.White + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = stringResource(if (profile.isFollowing) R.string.following_upper else R.string.follow_upper), + fontSize = 14.sp, + fontWeight = FontWeight.W600, + color = if (profile.isFollowing) Color.Black else Color.White, + modifier = Modifier.padding(8.dp), + ) + + } + Spacer(modifier = Modifier.width(8.dp)) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier + .clip(RoundedCornerShape(32.dp)) + .background(Color(0xffebebeb)) + .padding(horizontal = 16.dp, vertical = 4.dp) + .noRippleClickable { + onChat?.invoke() + } + ) { + Image( + painter = painterResource(id = R.drawable.rider_pro_comment), + contentDescription = "", + modifier = Modifier.size(24.dp), + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "CHAT", + fontSize = 14.sp, + fontWeight = FontWeight.W600, + color = Color.Black, + modifier = Modifier.padding(8.dp), + ) + + } } + // 按钮 + } + @Composable fun EmptyMomentPostUnit() { TimeGroup(stringResource(R.string.empty_my_post_title)) diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/MyProfileViewModel2.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/MyProfileViewModel2.kt deleted file mode 100644 index 8c569e0..0000000 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/MyProfileViewModel2.kt +++ /dev/null @@ -1,116 +0,0 @@ -package com.aiosman.riderpro.ui.index.tabs.profile.v2 - -import android.content.Context -import android.net.Uri -import android.util.Log -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.paging.Pager -import androidx.paging.PagingConfig -import androidx.paging.PagingData -import androidx.paging.cachedIn -import com.aiosman.riderpro.AppState -import com.aiosman.riderpro.AppStore -import com.aiosman.riderpro.data.AccountService -import com.aiosman.riderpro.data.AccountServiceImpl -import com.aiosman.riderpro.data.MomentService -import com.aiosman.riderpro.data.UploadImage -import com.aiosman.riderpro.entity.AccountProfileEntity -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 -import kotlinx.coroutines.launch - -object MyProfileViewModel2 : ViewModel() { - val accountService: AccountService = AccountServiceImpl() - val momentService: MomentService = MomentServiceImpl() - var profile by mutableStateOf(null) - private var _sharedFlow = MutableStateFlow>(PagingData.empty()) - var sharedFlow = _sharedFlow.asStateFlow() - - var refreshing by mutableStateOf(false) - var firstLoad = true - - fun loadProfile(pullRefresh: Boolean = false) { - viewModelScope.launch { - if (pullRefresh) { - refreshing = true - } - firstLoad = false - val profile = accountService.getMyAccountProfile() - MyProfileViewModel2.profile = profile - refreshing = false - try { - // Collect shared flow - Pager( - config = PagingConfig(pageSize = 5, enablePlaceholders = false), - pagingSourceFactory = { - MomentPagingSource( - MomentRemoteDataSource(momentService), - author = profile.id - ) - } - ).flow.cachedIn(viewModelScope).collectLatest { - _sharedFlow.value = it - } - } catch (e: Exception) { - Log.e("MyProfileViewModel", "loadProfile: ", e) - } - } - } - - suspend fun logout() { - AppStore.apply { - token = null - rememberMe = false - saveData() - } - AppState.ReloadAppState() - } - - fun updateUserProfileBanner(bannerImageUrl: Uri?, context: Context) { - viewModelScope.launch { - var newBanner = bannerImageUrl?.let { - val cursor = context.contentResolver.query(it, null, null, null, null) - var newBanner: UploadImage? = null - cursor?.use { cur -> - if (cur.moveToFirst()) { - val displayName = cur.getString(cur.getColumnIndex("_display_name")) - val extension = displayName.substringAfterLast(".") - Log.d("NewPost", "File name: $displayName, extension: $extension") - // read as file - val file = uriToFile(context, it) - Log.d("NewPost", "File size: ${file.length()}") - newBanner = UploadImage(file, displayName, it.toString(), extension) - } - } - newBanner - } - accountService.updateProfile( - banner = newBanner, - avatar = null, - nickName = null, - bio = null - ) - profile = accountService.getMyAccountProfile() - } - } - - val bio get() = profile?.bio ?: "" - val nickName get() = profile?.nickName ?: "" - val avatar get() = profile?.avatar - - fun ResetModel() { - profile = null - _sharedFlow.value = PagingData.empty() - firstLoad = true - } -} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile2.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile2.kt deleted file mode 100644 index 5fa526a..0000000 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile2.kt +++ /dev/null @@ -1,542 +0,0 @@ -package com.aiosman.riderpro.ui.index.tabs.profile.v2 - -import android.app.Activity -import android.content.Intent -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.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow -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.navigationBars -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.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Edit -import androidx.compose.material.pullrefresh.PullRefreshIndicator -import androidx.compose.material.pullrefresh.pullRefresh -import androidx.compose.material.pullrefresh.rememberPullRefreshState -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -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.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.LocalConfiguration -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -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.navigation.NavController -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.ui.NavigationRoute -import com.aiosman.riderpro.ui.composables.BottomNavigationPlaceholder -import com.aiosman.riderpro.ui.composables.CustomAsyncImage -import com.aiosman.riderpro.ui.composables.MenuItem -import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel -import com.aiosman.riderpro.ui.modifiers.noRippleClickable -import kotlinx.coroutines.launch - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun Profile2() { - val model = MyProfileViewModel2 - var expanded by remember { mutableStateOf(false) } - LaunchedEffect(Unit) { - MyProfileViewModel2.loadProfile() - } - val navController: NavController = LocalNavController.current - val scope = rememberCoroutineScope() - val state = rememberPullRefreshState(MyProfileViewModel2.refreshing, onRefresh = { - MyProfileViewModel2.loadProfile(pullRefresh = true) - }) - val context = LocalContext.current - val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() - val pickBannerImageLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.StartActivityForResult() - ) { result -> - if (result.resultCode == Activity.RESULT_OK) { - val uri = result.data?.data - uri?.let { - MyProfileViewModel2.updateUserProfileBanner(it, context = context) - } - } - } - - - val nestedScrollConnection = remember { - object : NestedScrollConnection { - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - // 处理滚动事件之前的逻辑 - return Offset.Zero - } - - override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { - // 处理滚动事件之后的逻辑 - return Offset.Zero - } - } - } - Box( - modifier = Modifier - .fillMaxSize() - .background(Color(0xFFF5F5F5)) - .padding(bottom = with(LocalDensity.current) { - val da = WindowInsets.navigationBars - .getBottom(this) - .toDp() + 48.dp - da - }) - .pullRefresh(state) - ) { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .nestedScroll(nestedScrollConnection), - ) { - item { - Column( - modifier = Modifier - .fillMaxWidth() - - ) { - // banner - Box( - modifier = Modifier - .fillMaxWidth() - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(500.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 = MyProfileViewModel2.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 - ) - } - } - 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 - ) - } - - com.aiosman.riderpro.ui.composables.DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - width = 250, - menuItems = listOf( - MenuItem( - stringResource(R.string.logout), - R.mipmap.rider_pro_logout - ) { - expanded = false - scope.launch { - MyProfileViewModel2.logout() - 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) - } - } - ), - - ) - } - } - } - Spacer(modifier = Modifier.height(32.dp)) - // 个人信息 - Box( - modifier = Modifier.padding(horizontal = 16.dp) - ) { - MyProfileViewModel2.profile?.let { - UserItem(it) - } - } - Spacer(modifier = Modifier.height(16.dp)) - MyProfileViewModel2.profile?.let { - Box( - modifier = Modifier.padding(horizontal = 16.dp) - ) { - SelfProfileAction { - navController.navigate(NavigationRoute.AccountEdit.route) - } - } - - } - Spacer(modifier = Modifier.height(16.dp)) - // 动态列表 - } - UserContentTab(nestedScrollConnection) - } - - - } - PullRefreshIndicator(MyProfileViewModel2.refreshing, state, Modifier.align(Alignment.TopCenter)) - } -} - -@Composable -fun UserItem(accountProfileEntity: AccountProfileEntity) { - Column( - modifier = Modifier - .fillMaxWidth() - ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - // 头像 - CustomAsyncImage( - LocalContext.current, - accountProfileEntity.avatar, - modifier = Modifier - .clip(CircleShape) - .size(48.dp), - contentDescription = "", - contentScale = ContentScale.Crop - ) - Spacer(modifier = Modifier.width(32.dp)) - //个人统计 - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.weight(1f) - ) { - Column( - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.weight(1f) - ) { - Text( - text = accountProfileEntity.followerCount.toString(), - fontWeight = FontWeight.W600, - fontSize = 16.sp - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource(R.string.followers_upper), - ) - } - Column( - verticalArrangement = Arrangement.Center, - modifier = Modifier.weight(1f), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Text( - text = accountProfileEntity.followingCount.toString(), - fontWeight = FontWeight.W600, - fontSize = 16.sp - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource(R.string.following_upper), - ) - } - Column( - verticalArrangement = Arrangement.Center, - modifier = Modifier.weight(1f), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - - } - } - } - Spacer(modifier = Modifier.height(12.dp)) - // 昵称 - Text( - text = accountProfileEntity.nickName, - fontWeight = FontWeight.W600, - fontSize = 16.sp, - ) - Spacer(modifier = Modifier.height(4.dp)) - // 个人简介 - Text( - text = accountProfileEntity.bio, - fontSize = 14.sp, - color = Color.Gray - ) - - } - -} - -@Composable -fun SelfProfileAction( - onEditProfile: () -> Unit -) { - // 按钮 - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - modifier = Modifier - .clip(RoundedCornerShape(32.dp)) - .background(Color(0xffebebeb)) - .padding(horizontal = 16.dp, vertical = 4.dp) - .noRippleClickable { - onEditProfile() - } - ) { - Icon( - Icons.Default.Edit, - contentDescription = "", - modifier = Modifier.size(24.dp) - ) - Spacer(modifier = Modifier.width(4.dp)) - Text( - text = stringResource(R.string.edit_profile), - fontSize = 14.sp, - fontWeight = FontWeight.W600, - color = Color.Black, - modifier = Modifier.padding(8.dp) - ) - - } -} - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun UserContentTab(nestedScrollConnection: NestedScrollConnection) { - val pagerState = rememberPagerState(pageCount = { 2 }) - val scope = rememberCoroutineScope() - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - modifier = Modifier - .clip(RoundedCornerShape(32.dp)) - .background(if (pagerState.currentPage == 0) Color(0xFFFFFFFF) else Color.Transparent) - .padding(horizontal = 16.dp, vertical = 4.dp) - .noRippleClickable { - // switch to gallery - scope.launch { - pagerState.scrollToPage(0) - } - - } - ) { - Text( - text = "Gallery", - fontSize = 14.sp, - fontWeight = FontWeight.W600, - color = Color.Black, - modifier = Modifier.padding(8.dp) - ) - - } - Spacer(modifier = Modifier.width(4.dp)) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - modifier = Modifier - .clip(RoundedCornerShape(32.dp)) - .background(if (pagerState.currentPage == 1) Color(0xFFFFFFFF) else Color.Transparent) - .padding(horizontal = 16.dp, vertical = 8.dp) - .noRippleClickable { - // switch to moments - scope.launch { - pagerState.scrollToPage(1) - } - } - ) { - Text( - text = "Moments", - fontSize = 16.sp, - fontWeight = FontWeight.W600, - color = Color.Black, - modifier = Modifier.padding(8.dp) - ) - } - } - Spacer(modifier = Modifier.height(16.dp)) - HorizontalPager( - state = pagerState, - modifier = Modifier - .padding(0.dp) - .height(900.dp), - beyondBoundsPageCount = 2, - verticalAlignment = Alignment.Top - ) { page -> - when (page) { - 0 -> Gallery() - 1 -> MomentsList(nestedScrollConnection) - } - } -} - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun Gallery() { - val moments = MyProfileViewModel2.sharedFlow.collectAsLazyPagingItems() - val screenWidth = LocalConfiguration.current.screenWidthDp.dp - val itemWidth = (screenWidth / 2) - 16.dp - 2.dp - FlowRow( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalArrangement = Arrangement.spacedBy(4.dp), - maxItemsInEachRow = 2, - ) { - for (idx in 0 until moments.itemCount) { - val moment = moments[idx] - moment?.let { - Box( - modifier = Modifier - .width(itemWidth) - .aspectRatio(1f) - .background(Color.Gray) - ) { - CustomAsyncImage( - LocalContext.current, - it.images[0].thumbnail, - modifier = Modifier - .fillMaxSize(), - contentDescription = "", - contentScale = ContentScale.Crop - ) - } - } - } - BottomNavigationPlaceholder() - } -} - -@Composable -fun MomentsList(nestedScrollConnection: NestedScrollConnection) { - val moments = MyProfileViewModel2.sharedFlow.collectAsLazyPagingItems() - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .height(900.dp).nestedScroll(nestedScrollConnection), - - ) { - item { - for (idx in 0 until moments.itemCount) { - val moment = moments[idx] - moment?.let { - MomentPostUnit(it) - } - } - } - - } -} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile3.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile3.kt deleted file mode 100644 index 38a57f1..0000000 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile3.kt +++ /dev/null @@ -1,134 +0,0 @@ -package com.aiosman.riderpro.ui.index.tabs.profile.v2 - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold -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.setValue -import androidx.compose.ui.Modifier -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.platform.LocalConfiguration -import androidx.compose.ui.unit.dp - - -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) -@Composable -fun Profile3() { - Scaffold( - - ) { it - val screenHeight = LocalConfiguration.current.screenHeightDp.dp - var headHeight by remember { mutableStateOf(400.dp) } - val speedFactor = 2 // 调整速度因子 -// val animatedHeadHeight by animateDpAsState( -// targetValue = headHeight, -// animationSpec = tween(durationMillis = 0) -// ) - val nestedScrollConnection = remember { - object : NestedScrollConnection { - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - val delta = available.y - val newHeight = (headHeight + delta.dp).coerceIn(0.dp, 400.dp) - headHeight = newHeight - return Offset.Zero - } - } - } - - LazyColumn( - modifier = Modifier - .fillMaxSize() - .nestedScroll(nestedScrollConnection) - ) { - item { - // head - Box( - modifier = Modifier - .fillMaxWidth() - .height(headHeight) - .background(Color.DarkGray) - ) - } - item { - LazyColumn( - modifier = Modifier.fillMaxWidth().height(screenHeight), - ) { - items(100) { idx -> - Box( - modifier = Modifier - .fillMaxWidth() - .height(64.dp) - .background(Color.Green) - ) { - Text("Item $idx") - } - Spacer(modifier = Modifier.height(8.dp)) - } - } - } -// item { -// HorizontalPager( -// state = rememberPagerState(pageCount = { 2 }), -// modifier = Modifier -// .fillMaxWidth() -// .height(screenHeight) -// ) { page -> -// when (page) { -// 0 -> { -// LazyVerticalGrid( -// columns = GridCells.Fixed(2), -// verticalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp), -// horizontalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp), -// modifier = Modifier.fillMaxSize(), -//// userScrollEnabled = headHeight < 2.dp -// ) { -// items(100) { idx -> -// Box( -// modifier = Modifier -// .fillMaxWidth() -// .height(64.dp) -// ) { -// Text("Item $idx") -// } -// } -// } -// } -// 1 -> { -// LazyColumn( -// modifier = Modifier.fillMaxSize(), -//// userScrollEnabled = headHeight < 2.dp -// ) { -// items(100) { idx -> -// Box( -// modifier = Modifier -// .fillMaxWidth() -// .height(64.dp) -// .background(Color.Green) -// ) { -// Text("Item $idx") -// } -// Spacer(modifier = Modifier.height(8.dp)) -// } -// } -// } -// } -// } -// } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile4.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile4.kt deleted file mode 100644 index 04a0332..0000000 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile4.kt +++ /dev/null @@ -1,134 +0,0 @@ -package com.aiosman.riderpro.ui.index.tabs.profile.v2 - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Scaffold -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.setValue -import androidx.compose.ui.Modifier -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.platform.LocalConfiguration -import androidx.compose.ui.unit.dp - - -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) -@Composable -fun Profile4() { - Scaffold( - - ) { it - val screenHeight = LocalConfiguration.current.screenHeightDp.dp - var headHeight by remember { mutableStateOf(400.dp) } - val speedFactor = 2 // 调整速度因子 -// val animatedHeadHeight by animateDpAsState( -// targetValue = headHeight, -// animationSpec = tween(durationMillis = 0) -// ) - val nestedScrollConnection = remember { - object : NestedScrollConnection { - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - val delta = available.y - val newHeight = (headHeight + delta.dp).coerceIn(0.dp, 400.dp) - headHeight = newHeight - return Offset.Zero - } - } - } - - LazyColumn( - modifier = Modifier - .fillMaxSize() - .nestedScroll(nestedScrollConnection) - ) { - item { - // head - Box( - modifier = Modifier - .fillMaxWidth() - .height(headHeight) - .background(Color.DarkGray) - ) - } - item { - LazyColumn( - modifier = Modifier.fillMaxWidth().height(screenHeight), - ) { - items(100) { idx -> - Box( - modifier = Modifier - .fillMaxWidth() - .height(64.dp) - .background(Color.Green) - ) { - Text("Item $idx") - } - Spacer(modifier = Modifier.height(8.dp)) - } - } - } -// item { -// HorizontalPager( -// state = rememberPagerState(pageCount = { 2 }), -// modifier = Modifier -// .fillMaxWidth() -// .height(screenHeight) -// ) { page -> -// when (page) { -// 0 -> { -// LazyVerticalGrid( -// columns = GridCells.Fixed(2), -// verticalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp), -// horizontalArrangement = androidx.compose.foundation.layout.Arrangement.spacedBy(8.dp), -// modifier = Modifier.fillMaxSize(), -//// userScrollEnabled = headHeight < 2.dp -// ) { -// items(100) { idx -> -// Box( -// modifier = Modifier -// .fillMaxWidth() -// .height(64.dp) -// ) { -// Text("Item $idx") -// } -// } -// } -// } -// 1 -> { -// LazyColumn( -// modifier = Modifier.fillMaxSize(), -//// userScrollEnabled = headHeight < 2.dp -// ) { -// items(100) { idx -> -// Box( -// modifier = Modifier -// .fillMaxWidth() -// .height(64.dp) -// .background(Color.Green) -// ) { -// Text("Item $idx") -// } -// Spacer(modifier = Modifier.height(8.dp)) -// } -// } -// } -// } -// } -// } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile5.kt b/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile5.kt deleted file mode 100644 index 398f993..0000000 --- a/app/src/main/java/com/aiosman/riderpro/ui/index/tabs/profile/v2/Profile5.kt +++ /dev/null @@ -1,879 +0,0 @@ -package com.aiosman.riderpro.ui.index.tabs.profile.v2 - -import android.app.Activity -import android.content.Intent -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.annotation.DrawableRes -import androidx.compose.foundation.Canvas -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.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow -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.navigationBars -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.layout.widthIn -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.pullrefresh.PullRefreshIndicator -import androidx.compose.material.pullrefresh.pullRefresh -import androidx.compose.material.pullrefresh.rememberPullRefreshState -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -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.clip -import androidx.compose.ui.draw.shadow -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.PathEffect -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.navigation.NavController -import com.aiosman.riderpro.AppState -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.exp.formatPostTime2 -import com.aiosman.riderpro.ui.NavigationRoute -import com.aiosman.riderpro.ui.composables.CustomAsyncImage -import com.aiosman.riderpro.ui.composables.MenuItem -import com.aiosman.riderpro.ui.index.tabs.profile.MyProfileViewModel -import com.aiosman.riderpro.ui.modifiers.noRippleClickable -import com.aiosman.riderpro.ui.navigateToChat -import com.aiosman.riderpro.ui.navigateToPost -import com.aiosman.riderpro.ui.post.NewPostViewModel -import kotlinx.coroutines.launch - - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun ProfilePage() { - val model = MyProfileViewModel2 - var expanded by remember { mutableStateOf(false) } - LaunchedEffect(Unit) { - MyProfileViewModel.loadProfile() - } - val navController: NavController = LocalNavController.current - val scope = rememberCoroutineScope() - val state = rememberPullRefreshState(MyProfileViewModel.refreshing, onRefresh = { - MyProfileViewModel.loadProfile(pullRefresh = true) - }) - val context = LocalContext.current - val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() - val pickBannerImageLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.StartActivityForResult() - ) { result -> - if (result.resultCode == Activity.RESULT_OK) { - val uri = result.data?.data - uri?.let { - MyProfileViewModel.updateUserProfileBanner(it, context = context) - } - } - } - Box( - modifier = Modifier - .fillMaxSize() - .background(Color(0xFFF5F5F5)) - .padding(bottom = with(LocalDensity.current) { - val da = WindowInsets.navigationBars - .getBottom(this) - .toDp() + 48.dp - da - }) - .pullRefresh(state) - ) { - - LazyColumn( - modifier = Modifier.fillMaxSize() - ) { - item { - Box( - modifier = Modifier - .fillMaxWidth() - - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(400.dp) - .noRippleClickable { - Intent(Intent.ACTION_PICK).apply { - type = "image/*" - pickBannerImageLauncher.launch(this) - } - } - ) { - val banner = MyProfileViewModel.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 - ) - } - } - 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 - ) - } - - com.aiosman.riderpro.ui.composables.DropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false }, - width = 250, - menuItems = listOf( - MenuItem( - stringResource(R.string.logout), - R.mipmap.rider_pro_logout - ) { - expanded = false - scope.launch { - MyProfileViewModel.logout() - 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) - } - } - ), - - ) - } - } - Spacer(modifier = Modifier.height(32.dp)) - MyProfileViewModel.profile?.let { - UserInformation( - accountProfileEntity = it, - onEditProfileClick = { - navController.navigate(NavigationRoute.AccountEdit.route) - } - ) - } -// if (moments.itemCount == 0) { -// EmptyMomentPostUnit() -// } - } - -// items(moments.itemCount) { idx -> -// val momentItem = moments[idx] ?: return@items -// MomentPostUnit(momentItem) -// } - item { - Spacer(modifier = Modifier.height(48.dp)) - } - - - } - PullRefreshIndicator(MyProfileViewModel.refreshing, state, Modifier.align(Alignment.TopCenter)) - } - - -} - -@Composable -fun CarGroup() { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(top = 54.dp), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - CarTopInformation() - CarTopPicture() - } -} - -@Composable -fun CarTopInformation() { - Row { - Text( - text = "BMW", - color = Color.Black, - fontSize = 12.sp, - style = TextStyle(fontWeight = FontWeight.Bold) - ) - Text( - modifier = Modifier.padding(start = 4.dp), - text = "/", - color = Color.Gray, - fontSize = 12.sp - ) - Text( - modifier = Modifier.padding(start = 4.dp), - text = "M1000RR", - color = Color.Gray, - fontSize = 12.sp - ) - } -} - -@Composable -fun CarTopPicture() { - Image( - modifier = Modifier - .size(width = 336.dp, height = 224.dp) - .padding(top = 42.dp), - painter = painterResource(id = R.drawable.default_profile_moto), contentDescription = "" - ) -} - -@Composable -fun UserInformation( - isSelf: Boolean = true, - accountProfileEntity: AccountProfileEntity, - onFollowClick: () -> Unit = {}, - onEditProfileClick: () -> Unit = {} -) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(top = 8.dp, start = 33.dp, end = 33.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Row(modifier = Modifier.fillMaxWidth()) { - val userInfoModifier = Modifier.weight(1f) - UserInformationFollowers(userInfoModifier, accountProfileEntity) - UserInformationBasic(userInfoModifier, accountProfileEntity) - UserInformationFollowing(userInfoModifier, accountProfileEntity) - } - UserInformationSlogan(accountProfileEntity) - CommunicationOperatorGroup( - isSelf = isSelf, - isFollowing = accountProfileEntity.isFollowing, - onFollowClick = onFollowClick, - onEditProfileClick = onEditProfileClick, - accountProfileEntity = accountProfileEntity - ) - } -} - -@Composable -fun UserInformationFollowers(modifier: Modifier, accountProfileEntity: AccountProfileEntity) { - val navController = LocalNavController.current - Column(modifier = modifier.padding(top = 31.dp)) { - Text( - modifier = Modifier - .padding(bottom = 5.dp) - .noRippleClickable { - navController.navigate( - NavigationRoute.FollowerList.route.replace( - "{id}", - accountProfileEntity.id.toString() - ) - ) - }, - text = accountProfileEntity.followerCount.toString(), - fontSize = 24.sp, - color = Color.Black, - style = TextStyle(fontStyle = FontStyle.Italic, fontWeight = FontWeight.Bold) - ) - Canvas( - modifier = Modifier - .size(width = 88.83.dp, height = 1.dp) - .padding(top = 2.dp, bottom = 5.dp) - ) { - drawLine( - color = Color(0xFFCCCCCC), - start = Offset(0f, 0f), - end = Offset(size.width, 0f), - strokeWidth = 1f, - pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f) - ) - } - Text( - modifier = Modifier.padding(top = 5.dp), - text = stringResource(R.string.followers_upper), - fontSize = 12.sp, - color = Color.Black, - style = TextStyle(fontWeight = FontWeight.Bold) - ) - } -} - -@Composable -fun UserInformationBasic(modifier: Modifier, accountProfileEntity: AccountProfileEntity) { - val context = LocalContext.current - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - Box( - modifier = Modifier.size(width = 112.dp, height = 112.dp), - contentAlignment = Alignment.Center - ) { - Image( - modifier = Modifier.fillMaxSize(), - painter = painterResource(id = R.drawable.avatar_bold), contentDescription = "" - ) - CustomAsyncImage( - context, - accountProfileEntity.avatar, - modifier = Modifier - .size(width = 88.dp, height = 88.dp) - .clip( - RoundedCornerShape(88.dp) - ), - contentDescription = "", - contentScale = ContentScale.Crop - ) - - } - Text( - modifier = Modifier - .widthIn(max = 220.dp) - .padding(top = 8.dp), - text = accountProfileEntity.nickName, - fontSize = 32.sp, - color = Color.Black, - style = TextStyle(fontWeight = FontWeight.Bold), - textAlign = TextAlign.Center - ) - Text( - modifier = Modifier.padding(top = 4.dp), - text = accountProfileEntity.country, - fontSize = 12.sp, - color = Color.Gray - ) - } -} - -@Composable -fun UserInformationFollowing(modifier: Modifier, accountProfileEntity: AccountProfileEntity) { - val navController = LocalNavController.current - Column( - modifier = modifier.padding(top = 6.dp), - horizontalAlignment = Alignment.End - ) { - Text( - modifier = Modifier - .padding(bottom = 5.dp) - .noRippleClickable { - navController.navigate( - NavigationRoute.FollowingList.route.replace( - "{id}", - accountProfileEntity.id.toString() - ) - ) - }, - text = accountProfileEntity.followingCount.toString(), - fontSize = 24.sp, - color = Color.Black, - style = TextStyle(fontStyle = FontStyle.Italic, fontWeight = FontWeight.Bold) - ) - Canvas( - modifier = Modifier - .size(width = 88.83.dp, height = 1.dp) - .padding(top = 2.dp, bottom = 5.dp) - ) { - drawLine( - color = Color(0xFFCCCCCC), - start = Offset(0f, 0f), - end = Offset(size.width, 0f), - strokeWidth = 1f, - pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f) - ) - } - Text( - modifier = Modifier.padding(top = 5.dp), - text = stringResource(R.string.following_upper), - fontSize = 12.sp, - color = Color.Black, - style = TextStyle(fontWeight = FontWeight.Bold) - ) - } -} - -@Composable -fun UserInformationSlogan(accountProfileEntity: AccountProfileEntity) { - Text( - modifier = Modifier.padding(top = 23.dp), - text = accountProfileEntity.bio, - fontSize = 13.sp, - color = Color.Black, - style = TextStyle(fontWeight = FontWeight.Bold) - ) -} - -@Composable -fun CommunicationOperatorGroup( - accountProfileEntity: AccountProfileEntity, - isSelf: Boolean = true, - isFollowing: Boolean = false, - onFollowClick: () -> Unit = {}, - onEditProfileClick: () -> Unit = {} -) { - val navController = LocalNavController.current - Row( - modifier = Modifier - .fillMaxWidth() - .padding(top = 16.dp), horizontalArrangement = Arrangement.Center - ) { - if (!isSelf && AppState.UserId != accountProfileEntity.id) { - Box( - modifier = Modifier - .size(width = 142.dp, height = 40.dp) - .noRippleClickable { - onFollowClick() - }, - contentAlignment = Alignment.Center - ) { - Image( - modifier = Modifier.fillMaxSize(), - painter = if (isFollowing) painterResource(id = R.mipmap.rider_pro_follow_grey) else painterResource( - id = R.mipmap.rider_pro_follow_red - ), - contentDescription = "" - ) - Text( - text = if (isFollowing) stringResource(R.string.following_upper) else stringResource( - R.string.follow_upper - ), - fontSize = 14.sp, - color = if (isFollowing) Color.Black else Color.White, - style = TextStyle(fontWeight = FontWeight.W600, fontStyle = FontStyle.Italic), - ) - } - Spacer(modifier = Modifier.width(16.dp)) - Box( - modifier = Modifier - .size(width = 142.dp, height = 40.dp) - .noRippleClickable { - navController.navigateToChat(accountProfileEntity.id.toString()) - }, - contentAlignment = Alignment.Center - ) { - Image( - modifier = Modifier.fillMaxSize(), - painter = painterResource(id = R.mipmap.rider_pro_btn_bg_grey), - contentDescription = "" - ) - Text( - text = "CHAT", - fontSize = 14.sp, - color = Color.Black, - fontWeight = FontWeight.Bold, - style = TextStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic) - ) - } - } - - if (isSelf) { - Box( - modifier = Modifier - .size(width = 142.dp, height = 40.dp) - .noRippleClickable { - onEditProfileClick() - }, - contentAlignment = Alignment.Center - ) { - Image( - modifier = Modifier.fillMaxSize(), - painter = painterResource(id = R.mipmap.rider_pro_btn_bg_grey), - contentDescription = "" - ) - Text( - text = stringResource(R.string.edit_profile), - fontSize = 14.sp, - color = Color.Black, - fontWeight = FontWeight.Bold, - style = TextStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic) - ) - } - } - } -} - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun RidingStyle() { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(start = 24.dp, top = 40.dp, end = 24.dp), - horizontalAlignment = Alignment.Start - ) { - Text( - text = "RIDING STYLES", - fontSize = 18.sp, - color = Color.Black, - style = TextStyle(fontWeight = FontWeight.Bold) - ) - Image( - modifier = Modifier - .padding(top = 4.dp) - .height(8.dp), - painter = painterResource(id = R.drawable.rider_pro_profile_line), - contentDescription = "" - ) - FlowRow( - modifier = Modifier - .fillMaxWidth() - .padding(top = 24.dp) - ) { - RidingStyleItem(styleContent = "Cruiser") - RidingStyleItem(styleContent = "Bobber") - RidingStyleItem(styleContent = "Cafe") - RidingStyleItem(styleContent = "Chopper") - RidingStyleItem(styleContent = "Sport") - RidingStyleItem(styleContent = "Vintage") - RidingStyleItem(styleContent = "Trike") - RidingStyleItem(styleContent = "Touring") - } - } -} - -@Composable -fun RidingStyleItem(styleContent: String) { - Box( - modifier = Modifier.padding(bottom = 8.dp, end = 8.dp), - contentAlignment = Alignment.Center - ) { - Image( - modifier = Modifier.shadow( - ambientColor = Color.Gray, - spotColor = Color(0f, 0f, 0f, 0.2f), - elevation = 20.dp, - ), - painter = painterResource(id = R.drawable.rider_pro_style_wrapper), - contentDescription = "" - ) - Text( - modifier = Modifier.padding(start = 5.dp, end = 5.dp), - text = styleContent, - fontSize = 12.sp, - color = Color.Gray, - style = TextStyle(fontWeight = FontWeight.Bold) - ) - } -} - -@Composable -fun EmptyMomentPostUnit() { - TimeGroup(stringResource(R.string.empty_my_post_title)) - ProfileEmptyMomentCard() -} - -@Composable -fun ProfileEmptyMomentCard( - -) { - var columnHeight by remember { mutableStateOf(0) } - val navController = LocalNavController.current - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(start = 24.dp, top = 18.dp, end = 24.dp) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - ) { - Canvas( - modifier = Modifier - .height(with(LocalDensity.current) { columnHeight.toDp() }) - .width(14.dp) - ) { - drawLine( - color = Color(0xff899DA9), - start = Offset(0f, 0f), - end = Offset(0f, size.height), - strokeWidth = 4f, - pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f) - ) - } - Spacer(modifier = Modifier.width(10.dp)) - Column( - modifier = Modifier - .weight(1f) - .onGloballyPositioned { coordinates -> - columnHeight = coordinates.size.height - } - ) { - Text(stringResource(R.string.empty_my_post_content), fontSize = 16.sp) - Spacer(modifier = Modifier.height(24.dp)) - Box( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(3f / 2f) - .background(Color.White) - .padding(16.dp) - ) { - Box( - modifier = Modifier - .fillMaxSize() - .background(Color(0xFFF5F5F5)) - .noRippleClickable { - NewPostViewModel.asNewPost() - navController.navigate(NavigationRoute.NewPost.route) - } - ) { - Icon( - Icons.Default.Add, - tint = Color(0xFFD8D8D8), - contentDescription = "New post", - modifier = Modifier - .size(32.dp) - .align(Alignment.Center) - ) - } - } - } - } - } -} - -@Composable -fun MomentPostUnit(momentEntity: MomentEntity) { - TimeGroup(momentEntity.time.formatPostTime2()) - ProfileMomentCard( - momentEntity.momentTextContent, - momentEntity.images[0].thumbnail, - momentEntity.likeCount.toString(), - momentEntity.commentCount.toString(), - momentEntity = momentEntity - ) -} - -@Composable -fun TimeGroup(time: String = "2024.06.08 12:23") { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(start = 24.dp, top = 40.dp, end = 24.dp), - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically - ) { - Image( - modifier = Modifier - .height(16.dp) - .width(14.dp), - painter = painterResource(id = R.drawable.rider_pro_moment_time_flag), - contentDescription = "" - ) - Spacer(modifier = Modifier.width(12.dp)) - Text( - text = time, - fontSize = 16.sp, - color = Color.Black, - style = TextStyle(fontWeight = FontWeight.W600) - ) - } -} - -@Composable -fun ProfileMomentCard( - content: String, - imageUrl: String, - like: String, - comment: String, - momentEntity: MomentEntity -) { - var columnHeight by remember { mutableStateOf(0) } - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(start = 24.dp, top = 18.dp, end = 24.dp) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - ) { - Canvas( - modifier = Modifier - .height(with(LocalDensity.current) { columnHeight.toDp() }) - .width(14.dp) - ) { - drawLine( - color = Color(0xff899DA9), - start = Offset(0f, 0f), - end = Offset(0f, size.height), - strokeWidth = 4f, - pathEffect = PathEffect.dashPathEffect(floatArrayOf(20f, 20f), 0f) - ) - } - Spacer(modifier = Modifier.width(10.dp)) - Column( - modifier = Modifier - .background(Color.White) - .weight(1f) - .onGloballyPositioned { coordinates -> - columnHeight = coordinates.size.height - } - ) { - if (content.isNotEmpty()) { - MomentCardTopContent(content) - } - MomentCardPicture(imageUrl, momentEntity = momentEntity) - MomentCardOperation(like, comment) - } - } - } -} - -@Composable -fun MomentCardTopContent(content: String) { - Row( - modifier = Modifier - .fillMaxWidth(), - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - modifier = Modifier.padding(top = 16.dp, bottom = 0.dp, start = 16.dp, end = 16.dp), - text = content, fontSize = 16.sp, color = Color.Black - ) - } -} - -@Composable -fun MomentCardPicture(imageUrl: String, momentEntity: MomentEntity) { - val navController = LocalNavController.current - val context = LocalContext.current - CustomAsyncImage( - context, - imageUrl, - modifier = Modifier - .fillMaxSize() - .aspectRatio(3f / 2f) - .padding(top = 16.dp) - .noRippleClickable { - navController.navigateToPost( - id = momentEntity.id, - highlightCommentId = 0, - initImagePagerIndex = 0 - ) - }, - contentDescription = "", - contentScale = ContentScale.Crop - ) - - -} - -@Composable -fun MomentCardOperation(like: String, comment: String) { - Row( - modifier = Modifier - .fillMaxWidth() - .height(48.dp), - horizontalArrangement = Arrangement.End, - verticalAlignment = Alignment.CenterVertically - ) { -// Spacer(modifier = Modifier.weight(1f)) - MomentCardOperationItem( - drawable = R.drawable.rider_pro_like, - number = like, - modifier = Modifier.padding(end = 32.dp) - ) - MomentCardOperationItem( - drawable = R.drawable.rider_pro_moment_comment, - number = comment, - modifier = Modifier.padding(end = 32.dp) - ) - } -} - -@Composable -fun MomentCardOperationItem(@DrawableRes drawable: Int, number: String, modifier: Modifier) { - Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically - ) { - Image( - modifier = Modifier.padding(start = 16.dp, end = 8.dp), - painter = painterResource(id = drawable), contentDescription = "" - ) - Text(text = number) - } -} \ No newline at end of file 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 ea21a36..ea623f5 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 @@ -38,6 +38,7 @@ import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.comment.NoticeScreenHeader import com.aiosman.riderpro.ui.composables.ActionButton import com.aiosman.riderpro.ui.composables.CheckboxWithLabel +import com.aiosman.riderpro.ui.composables.PolicyCheckbox import com.aiosman.riderpro.ui.composables.StatusBarSpacer import com.aiosman.riderpro.ui.composables.TextInputField import com.aiosman.riderpro.utils.Utils @@ -241,15 +242,21 @@ fun EmailSignupScreen() { rememberMe = it } Spacer(modifier = Modifier.height(16.dp)) - CheckboxWithLabel( + PolicyCheckbox( checked = acceptTerms, - checkSize = 16, - fontSize = 12, - label = stringResource(R.string.agree_terms_of_service), error = termsError ) { acceptTerms = it } +// CheckboxWithLabel( +// checked = acceptTerms, +// checkSize = 16, +// fontSize = 12, +// label = stringResource(R.string.agree_terms_of_service), +// error = termsError +// ) { +// acceptTerms = it +// } Spacer(modifier = Modifier.height(16.dp)) CheckboxWithLabel( checked = acceptPromotions, @@ -263,17 +270,23 @@ fun EmailSignupScreen() { } Spacer(modifier = Modifier.height(64.dp)) - ActionButton( - modifier = Modifier - .width(345.dp) - .height(48.dp), - text = stringResource(R.string.lets_ride_upper), - backgroundImage = R.mipmap.rider_pro_signup_red_bg + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center ) { - scope.launch(Dispatchers.IO) { - registerUser() + ActionButton( + modifier = Modifier + .width(345.dp) + .height(48.dp), + text = stringResource(R.string.lets_ride_upper), + backgroundImage = R.mipmap.rider_pro_signup_red_bg + ) { + scope.launch(Dispatchers.IO) { + registerUser() + } } } + } } diff --git a/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfile.kt b/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfile.kt deleted file mode 100644 index 30995ac..0000000 --- a/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfile.kt +++ /dev/null @@ -1,155 +0,0 @@ -package com.aiosman.riderpro.ui.profile - -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.Row -import androidx.compose.foundation.layout.Spacer -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.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.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel -import androidx.paging.compose.collectAsLazyPagingItems -import com.aiosman.riderpro.LocalNavController -import com.aiosman.riderpro.R -import com.aiosman.riderpro.exp.viewModelFactory -import com.aiosman.riderpro.ui.composables.CustomAsyncImage -import com.aiosman.riderpro.ui.composables.StatusBarSpacer -import com.aiosman.riderpro.ui.index.tabs.profile.v2.MomentPostUnit -import com.aiosman.riderpro.ui.index.tabs.profile.v2.UserInformation -import com.aiosman.riderpro.ui.modifiers.noRippleClickable -import kotlinx.coroutines.launch - -@OptIn(ExperimentalMaterialApi::class) -@Composable -fun AccountProfile(id: String) { - val model: AccountProfileViewModel = viewModel(factory = viewModelFactory { - AccountProfileViewModel() - }, key = "viewModel_${id}") - val scope = rememberCoroutineScope() - val items = model.momentsFlow.collectAsLazyPagingItems() - val state = rememberPullRefreshState(model.refreshing, onRefresh = { - model.loadProfile(id, pullRefresh = true) - }) - val navController = LocalNavController.current - LaunchedEffect(Unit) { - model.loadProfile(id) - } - Box( - modifier = Modifier - .fillMaxSize() - .pullRefresh(state) - .background(Color(0xFFF5F5F5)) - ) { - LazyColumn( - modifier = Modifier - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Top, - ) { - item { - Box( - modifier = Modifier - .fillMaxWidth() - .height(400.dp) - - ) { - if (model.profile != null) { - val banner = model.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 - ) - } - } - Box( - modifier = Modifier.fillMaxWidth() - ) { - Column( - modifier = Modifier.fillMaxWidth() - ) { - StatusBarSpacer() - Row( - modifier = Modifier.padding(vertical = 10.dp, horizontal = 18.dp) - ) { - Image( - painter = painterResource(id = R.drawable.rider_pro_nav_back), - contentDescription = "", - modifier = Modifier - .size(24.dp) - .noRippleClickable { - navController.popBackStack() - }, - colorFilter = ColorFilter.tint(Color.White) - ) - } - } - - } - - - } - Spacer(modifier = Modifier.height(32.dp)) - model.profile?.let { - UserInformation( - isSelf = false, - accountProfileEntity = it, - onFollowClick = { - scope.launch { - if (it.isFollowing) { - model.unFollowUser(id) - } else { - model.followUser(id) - } - } - }, - ) - } -// RidingStyle() - } - - items(items.itemCount) { idx -> - val momentItem = items[idx] ?: return@items - MomentPostUnit(momentItem) - } - item { - Spacer(modifier = Modifier.height(32.dp)) - } - - } - PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter)) - } - - -} \ No newline at end of file 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 new file mode 100644 index 0000000..08d11a9 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfileV2.kt @@ -0,0 +1,39 @@ +package com.aiosman.riderpro.ui.profile + +import androidx.compose.runtime.Composable +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.navigateToChat + +@Composable +fun AccountProfileV2(id: String){ + val model: AccountProfileViewModel = viewModel(factory = viewModelFactory { + AccountProfileViewModel() + }, key = "viewModel_${id}") + val navController = LocalNavController.current + LaunchedEffect(Unit) { + model.loadProfile(id) + } + Profile( + sharedFlow = model.momentsFlow, + profile = model.profile, + isSelf = false, + onChatClick = { + model.profile?.let { + navController.navigateToChat(it.id.toString()) + } + }, + onFollowClick = { + model.profile?.let { + if (it.isFollowing) { + model.unFollowUser(id) + } else { + model.followUser(id) + } + } + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfileViewModel.kt b/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfileViewModel.kt index c01d3f8..1f3d020 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfileViewModel.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/profile/AccountProfileViewModel.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch + class AccountProfileViewModel : ViewModel() { var profileId by mutableStateOf(0) val accountService: AccountService = AccountServiceImpl()