From 899d33cbc8f103ecfae73db392d90afa4bf60e94 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Tue, 21 Mar 2023 09:59:42 +0900 Subject: [PATCH 1/5] Improve banner --- .../java/io/github/akiomik/seiun/ui/user/UserScreen.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt b/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt index c6e699c..ae3ca5f 100644 --- a/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt +++ b/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt @@ -40,14 +40,15 @@ import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState @Composable private fun UserBanner(profile: Profile, height: Dp = 128.dp) { - if (profile.banner == null) { + Box { + // fallback Box( modifier = Modifier .background(color = Indigo800) .height(height) .fillMaxWidth() - ) {} - } else { + ) + AsyncImage( model = profile.banner, contentDescription = null, From adb7b03487203eb6274160e07077837a43eb93d4 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Tue, 21 Mar 2023 13:08:41 +0900 Subject: [PATCH 2/5] Add StatRow --- .../akiomik/seiun/ui/user/UserScreen.kt | 43 ++++++++++++++++++- app/src/main/res/values-ja-rJP/strings.xml | 3 ++ app/src/main/res/values/strings.xml | 3 ++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt b/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt index ae3ca5f..296c138 100644 --- a/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt +++ b/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt @@ -27,10 +27,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage +import io.github.akiomik.seiun.R import io.github.akiomik.seiun.model.app.bsky.actor.Profile import io.github.akiomik.seiun.ui.theme.Indigo800 import io.github.akiomik.seiun.viewmodels.UserFeedViewModel @@ -88,7 +91,43 @@ private fun NameAndHandle(profile: Profile) { } @Composable -fun Profile(profile: Profile) { +private fun StatRow(profile: Profile) { + Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = profile.followsCount.toString(), + fontWeight = FontWeight.Bold + ) + Text( + text = stringResource(R.string.follows), + style = MaterialTheme.typography.labelMedium + ) + } + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = profile.followersCount.toString(), + fontWeight = FontWeight.Bold + ) + Text( + text = stringResource(R.string.followers), + style = MaterialTheme.typography.labelMedium + ) + } + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = profile.postsCount.toString(), + fontWeight = FontWeight.Bold + ) + Text( + text = stringResource(R.string.posts), + style = MaterialTheme.typography.labelMedium + ) + } + } +} + +@Composable +private fun Profile(profile: Profile) { Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) @@ -102,6 +141,8 @@ fun Profile(profile: Profile) { } Text(profile.description.orEmpty()) + + StatRow(profile = profile) } } diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index adb5bd2..437c1ca 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -15,6 +15,9 @@ ライセンス ログイン サービスプロバイダ + フォロー中 + フォロワー + 投稿 ハンドルネーム または メールアドレス jack.bsky.social または jack@example.com パスワード diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dde2801..a0ae0ca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,6 +13,9 @@ Mute License Service Provider + follows + followers + posts Login Handle or Email jack.bsky.social or jack@example.com From e58475e207775549f0f86ff838653196c19e8628 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Tue, 21 Mar 2023 21:17:55 +0900 Subject: [PATCH 3/5] Improve UserScreen layout --- .../akiomik/seiun/ui/user/UserScreen.kt | 48 ++++++++++++++----- app/src/main/res/values-ja-rJP/strings.xml | 3 ++ app/src/main/res/values/strings.xml | 3 ++ 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt b/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt index 296c138..bb3a384 100644 --- a/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt +++ b/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt @@ -8,9 +8,11 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme @@ -31,11 +33,13 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import io.github.akiomik.seiun.R import io.github.akiomik.seiun.model.app.bsky.actor.Profile import io.github.akiomik.seiun.ui.theme.Indigo800 +import io.github.akiomik.seiun.viewmodels.AppViewModel import io.github.akiomik.seiun.viewmodels.UserFeedViewModel import me.onebone.toolbar.CollapsingToolbarScaffold import me.onebone.toolbar.ScrollStrategy @@ -62,13 +66,12 @@ private fun UserBanner(profile: Profile, height: Dp = 128.dp) { } @Composable -private fun Avatar(profile: Profile) { +private fun Avatar(profile: Profile, modifier: Modifier = Modifier, size: Dp = 64.dp) { AsyncImage( model = profile.avatar, contentDescription = null, - modifier = Modifier - .width(60.dp) - .height(60.dp) + modifier = modifier + .size(size) .clip(CircleShape) ) } @@ -128,18 +131,27 @@ private fun StatRow(profile: Profile) { @Composable private fun Profile(profile: Profile) { + val viewModel: AppViewModel = viewModel() + val viewer by viewModel.profile.collectAsState() + Column( - modifier = Modifier.padding(16.dp), + modifier = Modifier.padding(top = 8.dp, end = 16.dp, bottom = 16.dp, start = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Row( - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Avatar(profile = profile) - NameAndHandle(profile = profile) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { + if (profile.did == viewer?.did) { + Button(onClick = {}) { + Text(stringResource(R.string.edit)) + } + } else { + Button(onClick = {}) { + Text(stringResource(R.string.follow)) + } + } } + NameAndHandle(profile = profile) + Text(profile.description.orEmpty()) StatRow(profile = profile) @@ -148,12 +160,22 @@ private fun Profile(profile: Profile) { @Composable private fun UserModalContent(profile: Profile, onProfileClick: (String) -> Unit) { + val bannerHeight = 128.dp + val avatarSize = 96.dp + // TODO: Use ExitUntilCollapsed CollapsingToolbarScaffold( state = rememberCollapsingToolbarScaffoldState(), toolbar = { Column { - UserBanner(profile) + Box(modifier = Modifier.zIndex(2f)) { + UserBanner(profile, height = bannerHeight) + Avatar( + profile = profile, + modifier = Modifier.offset(x = 16.dp, y = bannerHeight - (avatarSize / 2)), + size = avatarSize + ) + } Box( modifier = Modifier .background(color = MaterialTheme.colorScheme.surfaceVariant) diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 437c1ca..f914b57 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -18,6 +18,9 @@ フォロー中 フォロワー 投稿 + 編集 + フォロー + フォロー解除 ハンドルネーム または メールアドレス jack.bsky.social または jack@example.com パスワード diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a0ae0ca..f85256a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,6 +16,9 @@ follows followers posts + Edit + Follow + Unfollow Login Handle or Email jack.bsky.social or jack@example.com From 841e03f2512ebfea9b4483b7d7ccf32bc4935a28 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Tue, 21 Mar 2023 21:28:32 +0900 Subject: [PATCH 4/5] Make AppViewModel singleton --- .../akiomik/seiun/viewmodels/AppViewModel.kt | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/io/github/akiomik/seiun/viewmodels/AppViewModel.kt b/app/src/main/java/io/github/akiomik/seiun/viewmodels/AppViewModel.kt index 85a647d..2858591 100644 --- a/app/src/main/java/io/github/akiomik/seiun/viewmodels/AppViewModel.kt +++ b/app/src/main/java/io/github/akiomik/seiun/viewmodels/AppViewModel.kt @@ -2,7 +2,6 @@ package io.github.akiomik.seiun.viewmodels import android.util.Log import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.lifecycle.viewModelScope import io.github.akiomik.seiun.SeiunApplication import io.github.akiomik.seiun.model.app.bsky.actor.Profile @@ -13,62 +12,63 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn -class AppViewModel : ApplicationViewModel() { - private val _atpService = SeiunApplication.instance!!.atpService +object AppViewModel : ApplicationViewModel() { + private val innerAtpService = SeiunApplication.instance!!.atpService - private var _profile = MutableStateFlow(null) - private var _showDrawer = MutableStateFlow(false) - private var _showTopBar = MutableStateFlow(false) - private var _showBottomBar = MutableStateFlow(false) - private var _fab = MutableStateFlow<@Composable () -> Unit>({}) + private var innerProfile = MutableStateFlow(null) + private var innerShowDrawer = MutableStateFlow(false) + private var innerShowTopBar = MutableStateFlow(false) + private var innerShowBottomBar = MutableStateFlow(false) + private var innerFab = MutableStateFlow<@Composable () -> Unit>({}) - private val isDrawerAvailable = _atpService.combine(_profile) { atpService, profile -> + private val isDrawerAvailable = innerAtpService.combine(innerProfile) { atpService, profile -> atpService != null && profile != null } - val profile = _profile.asStateFlow() - val showDrawer = _showDrawer.combine(isDrawerAvailable) { showDrawer, isAvailable -> + val profile = innerProfile.asStateFlow() + val showDrawer = innerShowDrawer.combine(isDrawerAvailable) { showDrawer, isAvailable -> showDrawer && isAvailable }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false) - val showTopBar = _showTopBar.asStateFlow() - val showBottomBar = _showBottomBar.asStateFlow() - val fab = _fab.asStateFlow() + val showTopBar = innerShowTopBar.asStateFlow() + val showBottomBar = innerShowBottomBar.asStateFlow() + val fab = innerFab.asStateFlow() private val userRepository = SeiunApplication.instance!!.userRepository fun updateProfile() { wrapError( run = { userRepository.getProfile() }, - onSuccess = { _profile.value = it }, + onSuccess = { innerProfile.value = it }, onError = { Log.d(SeiunApplication.TAG, "Failed to init ProfileViewModel: $it") } ) } fun onTimeline() { - _showDrawer.value = true - _showTopBar.value = true - _showBottomBar.value = true - _fab.value = { NewPostFab() } + innerShowDrawer.value = true + innerShowTopBar.value = true + innerShowBottomBar.value = true + innerFab.value = { NewPostFab() } } fun onNotification() { - _showDrawer.value = true - _showTopBar.value = true - _showBottomBar.value = true - _fab.value = {} + innerShowDrawer.value = true + innerShowTopBar.value = true + innerShowBottomBar.value = true + innerFab.value = {} } fun onUser() { - _showDrawer.value = false - _showTopBar.value = false - _showBottomBar.value = true - _fab.value = {} + innerShowDrawer.value = false + innerShowTopBar.value = false + innerShowBottomBar.value = true + innerFab.value = {} } fun onLoginOrRegistration() { - _showDrawer.value = false - _showTopBar.value = false - _showBottomBar.value = false - _fab.value = {} + innerProfile.value = null + innerShowDrawer.value = false + innerShowTopBar.value = false + innerShowBottomBar.value = false + innerFab.value = {} } } From f8f23902f49ff4fb468c5c25892bc3e4f4211e52 Mon Sep 17 00:00:00 2001 From: Akiomi Kamakura Date: Tue, 21 Mar 2023 21:35:45 +0900 Subject: [PATCH 5/5] Hide Edit and Follow button --- .../akiomik/seiun/ui/user/UserScreen.kt | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt b/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt index bb3a384..74f3c71 100644 --- a/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt +++ b/app/src/main/java/io/github/akiomik/seiun/ui/user/UserScreen.kt @@ -5,6 +5,7 @@ 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 @@ -12,7 +13,6 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme @@ -139,15 +139,17 @@ private fun Profile(profile: Profile) { verticalArrangement = Arrangement.spacedBy(16.dp) ) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { - if (profile.did == viewer?.did) { - Button(onClick = {}) { - Text(stringResource(R.string.edit)) - } - } else { - Button(onClick = {}) { - Text(stringResource(R.string.follow)) - } - } + // TODO: Implement edit and follow + Spacer(modifier = Modifier.height(32.dp)) +// if (profile.did == viewer?.did) { +// Button(onClick = {}) { +// Text(stringResource(R.string.edit)) +// } +// } else { +// Button(onClick = {}) { +// Text(stringResource(R.string.follow)) +// } +// } } NameAndHandle(profile = profile)