diff --git a/app/src/main/java/com/aiosman/riderpro/MainActivity.kt b/app/src/main/java/com/aiosman/riderpro/MainActivity.kt index 5bcaa6a..bac6373 100644 --- a/app/src/main/java/com/aiosman/riderpro/MainActivity.kt +++ b/app/src/main/java/com/aiosman/riderpro/MainActivity.kt @@ -31,22 +31,45 @@ import androidx.compose.ui.unit.dp import androidx.core.view.WindowCompat import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState +import com.aiosman.riderpro.data.TestUserServiceImpl +import com.aiosman.riderpro.data.UserService import com.aiosman.riderpro.ui.Navigation +import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.index.NavigationItem import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.android.libraries.places.api.Places +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { - + private val scope = CoroutineScope(Dispatchers.Main) + suspend fun getAccount() { + //TODO apply token to client + if (!AppStore.rememberMe) { + return + } + val userService: UserService = TestUserServiceImpl() + userService.getMyAccount() + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) if (!Places.isInitialized()) { Places.initialize(applicationContext, "AIzaSyDpgLDH1-SECw_pdjJq_msynq1XrxwgKVI") } + AppStore.init(this) enableEdgeToEdge() - setContent { - Navigation() + + scope.launch { + getAccount() + var startDestination = NavigationRoute.Login.route + if (AppStore.token != null && AppStore.rememberMe) { + startDestination = NavigationRoute.Index.route + } + setContent { + Navigation(startDestination) + } } } } diff --git a/app/src/main/java/com/aiosman/riderpro/data/UserService.kt b/app/src/main/java/com/aiosman/riderpro/data/UserService.kt index cf4a432..58e3120 100644 --- a/app/src/main/java/com/aiosman/riderpro/data/UserService.kt +++ b/app/src/main/java/com/aiosman/riderpro/data/UserService.kt @@ -2,8 +2,16 @@ package com.aiosman.riderpro.data import com.aiosman.riderpro.test.TestDatabase +data class UserAuth( + val id: Int, + val token: String? = null +) + interface UserService { - suspend fun getUserProfile(id:String): AccountProfile + suspend fun getUserProfile(id: String): AccountProfile + suspend fun getMyAccount(): UserAuth + suspend fun loginUserWithPassword(loginName: String, password: String): UserAuth + suspend fun logout() } class TestUserServiceImpl : UserService { @@ -15,4 +23,17 @@ class TestUserServiceImpl : UserService { } return AccountProfile(0, 0, 0, "", "", "", "") } + + override suspend fun getMyAccount(): UserAuth { + return UserAuth(1) + } + + override suspend fun loginUserWithPassword(loginName: String, password: String): UserAuth { + return UserAuth(1, "token") + } + + override suspend fun logout() { + // do nothing + } + } \ No newline at end of file diff --git a/app/src/main/java/com/aiosman/riderpro/store.kt b/app/src/main/java/com/aiosman/riderpro/store.kt new file mode 100644 index 0000000..c1dd0f0 --- /dev/null +++ b/app/src/main/java/com/aiosman/riderpro/store.kt @@ -0,0 +1,34 @@ +package com.aiosman.riderpro + +import android.content.Context +import android.content.SharedPreferences + +/** + * 持久化本地数据 + */ +object AppStore { + private const val STORE_VERSION = 1 + private const val PREFS_NAME = "app_prefs_$STORE_VERSION" + var token: String? = null + var rememberMe: Boolean = false + private lateinit var sharedPreferences: SharedPreferences + + fun init(context: Context) { + sharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + this.loadData() + } + + suspend fun saveData() { + // shared preferences + sharedPreferences.edit().apply { + putString("token", token) + putBoolean("rememberMe", rememberMe) + }.apply() + } + + fun loadData() { + // shared preferences + token = sharedPreferences.getString("token", null) + rememberMe = sharedPreferences.getBoolean("rememberMe", false) + } +} \ 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 7414816..0b85c1f 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/Navi.kt @@ -64,14 +64,14 @@ sealed class NavigationRoute( @Composable -fun NavigationController(navController: NavHostController) { +fun NavigationController(navController: NavHostController,startDestination: String = NavigationRoute.Login.route) { val navigationBarHeight = with(LocalDensity.current) { WindowInsets.navigationBars.getBottom(this).toDp() } NavHost( navController = navController, - startDestination = NavigationRoute.Index.route, + startDestination = startDestination, ) { composable(route = NavigationRoute.Index.route) { CompositionLocalProvider( @@ -170,7 +170,7 @@ fun NavigationController(navController: NavHostController) { @OptIn(ExperimentalSharedTransitionApi::class) @Composable -fun Navigation() { +fun Navigation(startDestination: String = NavigationRoute.Login.route) { val navController = rememberNavController() SharedTransitionLayout { CompositionLocalProvider( @@ -178,7 +178,7 @@ fun Navigation() { LocalSharedTransitionScope provides this@SharedTransitionLayout, ) { Box { - NavigationController(navController = navController) + NavigationController(navController = navController,startDestination = startDestination) } } } 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 66ee37a..372b5fe 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 @@ -66,14 +66,6 @@ fun IndexScreen() { selected = isSelected, onClick = { model.tabIndex = idx -// if (it.route == NavigationItem.Add.route || it.route == NavigationItem.Message.route) { -// systemUiController.setStatusBarColor(Color.Black, darkIcons = false) -// } else { -// systemUiController.setStatusBarColor( -// Color.Transparent, -// darkIcons = true -// ) -// } }, colors = NavigationBarItemColors( selectedTextColor = Color.Red, 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 150188e..100da9a 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 @@ -3,20 +3,25 @@ package com.aiosman.riderpro.ui.index.tabs.profile import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.navigation.NavController import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData +import com.aiosman.riderpro.AppStore +import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.data.AccountProfile import com.aiosman.riderpro.data.AccountService import com.aiosman.riderpro.data.TestAccountServiceImpl import com.aiosman.riderpro.data.MomentPagingSource import com.aiosman.riderpro.data.MomentRemoteDataSource import com.aiosman.riderpro.data.TestMomentServiceImpl +import com.aiosman.riderpro.data.TestUserServiceImpl import com.aiosman.riderpro.model.MomentItem import kotlinx.coroutines.flow.Flow object MyProfileViewModel { val service: AccountService = TestAccountServiceImpl() + val userService = TestUserServiceImpl() var profile by mutableStateOf(null) var momentsFlow by mutableStateOf>?>(null) suspend fun loadProfile() { @@ -32,6 +37,15 @@ object MyProfileViewModel { } ).flow } + suspend fun logout() { + service.getMyAccountProfile() + AppStore.apply { + token = null + rememberMe = false + saveData() + } + + } val followerCount get() = profile?.followerCount ?: 0 val followingCount get() = profile?.followingCount ?: 0 val bio get() = profile?.bio ?: "" 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 104566e..acd2e6a 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 @@ -20,9 +20,17 @@ import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +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 @@ -35,44 +43,90 @@ 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 androidx.paging.compose.collectAsLazyPagingItems import coil.compose.AsyncImage import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.R import com.aiosman.riderpro.data.AccountProfile import com.aiosman.riderpro.model.MomentItem +import com.aiosman.riderpro.ui.NavigationRoute +import com.aiosman.riderpro.ui.modifiers.noRippleClickable +import kotlinx.coroutines.launch @Composable fun ProfilePage() { val model = MyProfileViewModel + var expanded by remember { mutableStateOf(false) } LaunchedEffect(Unit) { model.loadProfile() } val moments = model.momentsFlow?.collectAsLazyPagingItems() - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(bottom = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Top, - ) { - item { - CarGroup() - model.profile?.let { - UserInformation(accountProfile = it) + val navController: NavController = LocalNavController.current + val scope = rememberCoroutineScope() + Box { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(bottom = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Top, + ) { + + item { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp, start = 16.dp, end = 16.dp) + ) { + Box( + modifier = Modifier.align(Alignment.TopEnd) + ) { + Icon( + painter = painterResource(id = R.drawable.rider_pro_more_horizon), + contentDescription = "", + modifier = Modifier.noRippleClickable { + expanded = true + } + ) + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem(onClick = { + scope.launch { + model.logout() + navController.navigate(NavigationRoute.Login.route) { + popUpTo(NavigationRoute.Index.route) { + inclusive = true + } + } + } + }, text = { + Text("Logout") + }) + } + } + + } + CarGroup() + model.profile?.let { + UserInformation(accountProfile = it) + } + + RidingStyle() + } + moments?.let { + items(it.itemCount) { idx -> + val momentItem = it[idx] ?: return@items + MomentPostUnit(momentItem) + } } - RidingStyle() } - moments?.let { - items(it.itemCount) { idx -> - val momentItem = it[idx] ?: return@items - MomentPostUnit(momentItem) - } - } - } + } @Composable @@ -123,7 +177,7 @@ fun CarTopPicture() { } @Composable -fun UserInformation(isSelf: Boolean = true,accountProfile: AccountProfile) { +fun UserInformation(isSelf: Boolean = true, accountProfile: AccountProfile) { Column( modifier = Modifier .fillMaxWidth() @@ -132,9 +186,9 @@ fun UserInformation(isSelf: Boolean = true,accountProfile: AccountProfile) { ) { Row(modifier = Modifier.fillMaxWidth()) { val userInfoModifier = Modifier.weight(1f) - UserInformationFollowers(userInfoModifier,accountProfile) - UserInformationBasic(userInfoModifier,accountProfile) - UserInformationFollowing(userInfoModifier,accountProfile) + UserInformationFollowers(userInfoModifier, accountProfile) + UserInformationBasic(userInfoModifier, accountProfile) + UserInformationFollowing(userInfoModifier, accountProfile) } UserInformationSlogan() CommunicationOperatorGroup(isSelf = isSelf) @@ -142,7 +196,7 @@ fun UserInformation(isSelf: Boolean = true,accountProfile: AccountProfile) { } @Composable -fun UserInformationFollowers(modifier: Modifier,accountProfile: AccountProfile) { +fun UserInformationFollowers(modifier: Modifier, accountProfile: AccountProfile) { Column(modifier = modifier.padding(top = 31.dp)) { Text( modifier = Modifier.padding(bottom = 5.dp), @@ -168,7 +222,7 @@ fun UserInformationFollowers(modifier: Modifier,accountProfile: AccountProfile) } @Composable -fun UserInformationBasic(modifier: Modifier,accountProfile: AccountProfile) { +fun UserInformationBasic(modifier: Modifier, accountProfile: AccountProfile) { Column( horizontalAlignment = Alignment.CenterHorizontally ) { @@ -193,7 +247,9 @@ fun UserInformationBasic(modifier: Modifier,accountProfile: AccountProfile) { } Text( - modifier = Modifier.widthIn(max =220.dp).padding(top = 8.dp), + modifier = Modifier + .widthIn(max = 220.dp) + .padding(top = 8.dp), text = accountProfile.nickName, fontSize = 32.sp, color = Color.Black, @@ -216,7 +272,7 @@ fun UserInformationBasic(modifier: Modifier,accountProfile: AccountProfile) { } @Composable -fun UserInformationFollowing(modifier: Modifier,accountProfile: AccountProfile) { +fun UserInformationFollowing(modifier: Modifier, accountProfile: AccountProfile) { Column( modifier = modifier.padding(top = 6.dp), horizontalAlignment = Alignment.End @@ -264,22 +320,25 @@ fun CommunicationOperatorGroup(isSelf: Boolean = true) { .fillMaxWidth() .padding(top = 16.dp), horizontalArrangement = Arrangement.Center ) { - Box( - modifier = Modifier.size(width = 142.dp, height = 40.dp), - contentAlignment = Alignment.Center - ) { - Image( - modifier = Modifier.fillMaxSize(), - painter = painterResource(id = R.drawable.rider_pro_profile_follow), - contentDescription = "" - ) - Text( - text = "FOLLOW", - fontSize = 16.sp, - color = Color.White, - style = TextStyle(fontWeight = FontWeight.Bold) - ) + if (!isSelf) { + Box( + modifier = Modifier.size(width = 142.dp, height = 40.dp), + contentAlignment = Alignment.Center + ) { + Image( + modifier = Modifier.fillMaxSize(), + painter = painterResource(id = R.drawable.rider_pro_profile_follow), + contentDescription = "" + ) + Text( + text = "FOLLOW", + fontSize = 16.sp, + color = Color.White, + style = TextStyle(fontWeight = FontWeight.Bold) + ) + } } + if (isSelf) { Box( modifier = Modifier @@ -469,7 +528,7 @@ fun MomentCardTopContent(content: String) { } @Composable -fun MomentCardPicture(imageUrl:String) { +fun MomentCardPicture(imageUrl: String) { AsyncImage( imageUrl, modifier = Modifier diff --git a/app/src/main/java/com/aiosman/riderpro/ui/login/userauth.kt b/app/src/main/java/com/aiosman/riderpro/ui/login/userauth.kt index 8a9adf2..e2bb5a4 100644 --- a/app/src/main/java/com/aiosman/riderpro/ui/login/userauth.kt +++ b/app/src/main/java/com/aiosman/riderpro/ui/login/userauth.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -33,10 +34,17 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.aiosman.riderpro.AppStore +import com.aiosman.riderpro.LocalNavController import com.aiosman.riderpro.R +import com.aiosman.riderpro.data.TestUserServiceImpl +import com.aiosman.riderpro.data.UserService +import com.aiosman.riderpro.ui.NavigationRoute import com.aiosman.riderpro.ui.comment.NoticeScreenHeader import com.aiosman.riderpro.ui.composables.StatusBarMaskLayout import com.aiosman.riderpro.ui.modifiers.noRippleClickable +import kotlinx.coroutines.launch + @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -44,6 +52,25 @@ fun UserAuthScreen() { var email by remember { mutableStateOf("") } var password by remember { mutableStateOf("") } var rememberMe by remember { mutableStateOf(false) } + var userService: UserService = TestUserServiceImpl() + val scope = rememberCoroutineScope() + val navController = LocalNavController.current + fun onLogin() { + scope.launch { + val authResp = userService.loginUserWithPassword(email, password) + if (authResp.token != null) { + AppStore.apply { + token = authResp.token + this.rememberMe = rememberMe + saveData() + } + navController.navigate(NavigationRoute.Index.route) { + popUpTo(NavigationRoute.Login.route) { inclusive = true } + } + } + } + + } StatusBarMaskLayout { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -106,7 +133,9 @@ fun UserAuthScreen() { .height(48.dp), text = "LET'S RIDE".uppercase(), backgroundImage = R.mipmap.rider_pro_signup_red_bg - ) + ) { + onLogin() + } Spacer(modifier = Modifier.height(121.dp)) Text("or login with", color = Color(0xFF999999)) @@ -153,9 +182,11 @@ fun TextInputField( Image( painter = painterResource(id = R.drawable.rider_pro_eye), contentDescription = "Password", - modifier = Modifier.size(24.dp).noRippleClickable { - showPassword = !showPassword - }, + modifier = Modifier + .size(24.dp) + .noRippleClickable { + showPassword = !showPassword + }, colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(Color.Black) ) }