diff --git a/desktopApp/src/main/composeResources/drawable/flare_logo.xml b/desktopApp/src/main/composeResources/drawable/flare_logo.xml index 8c6da144e..e08e12672 100644 --- a/desktopApp/src/main/composeResources/drawable/flare_logo.xml +++ b/desktopApp/src/main/composeResources/drawable/flare_logo.xml @@ -1,100 +1,125 @@ - + android:width="341dp" + android:height="341dp" + android:viewportWidth="341" + android:viewportHeight="341"> + + + android:pathData="M340.16,0H0V340.16H340.16V0Z"> - - - + + + android:pathData="M192.77,237.7C160.98,256.29 167.58,290.08 172.15,306.46C174.49,306.46 176.87,306.39 179.27,306.22C215.2,303.71 250.13,274.49 258.94,235.1C262.22,220.45 261.46,204.64 257.53,188.26C252.54,215.81 227.54,217.36 192.78,237.69L192.77,237.7Z"> - - - + + + + + + android:pathData="M88.24,175.88C82.9,187.68 81.73,193.76 80.29,202.56C72.53,249.53 105.33,299.66 160.23,305.83C130.09,269.86 114.2,224.3 166.99,161.89C200.31,122.52 201.51,84.42 173.43,33.69C175.82,105.07 92.81,126.83 105.45,201.9C95.42,197 88.25,175.88 88.25,175.88H88.24Z"> - - - + + + + + + android:pathData="M172.16,306.46C167.59,290.08 160.98,256.3 192.78,237.68C227.55,217.36 252.56,215.82 257.55,188.26C261.35,245.2 182.91,240.63 172.16,306.46Z"> - - - + + + + + + android:pathData="M154.38,253.26C165.52,212.06 232.57,203.01 243.19,170.81C250.56,148.47 237.46,121.32 224.53,108.9C238.11,169.4 135.48,184.86 154.39,253.26H154.38Z"> - - - + + + + + + android:pathData="M105.44,201.84C105.44,201.84 105.46,201.88 105.47,201.9C115.78,146.63 200.2,139.8 191.57,80.45C188.8,65.92 182.74,50.51 173.44,33.7C175.83,105.06 92.87,126.83 105.45,201.84H105.44Z"> - - - + + + + + + + + + + + + + + + + diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Route.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Route.kt index 6c9a69b40..c46cefdf5 100644 --- a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Route.kt +++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Route.kt @@ -228,6 +228,18 @@ internal sealed interface Route { @Serializable data object StorageUsage : ScreenRoute + @Serializable + data class Following( + val accountType: AccountType, + val userKey: MicroBlogKey, + ) : ScreenRoute + + @Serializable + data class Fans( + val accountType: AccountType, + val userKey: MicroBlogKey, + ) : ScreenRoute + companion object { public fun parse(url: String): Route? { val data = Url(url) diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Router.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Router.kt index c76cdde53..e68212a8a 100644 --- a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Router.kt +++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Router.kt @@ -30,6 +30,8 @@ import dev.dimension.flare.ui.screen.dm.DmListScreen import dev.dimension.flare.ui.screen.dm.UserDMConversationScreen import dev.dimension.flare.ui.screen.feeds.FeedListScreen import dev.dimension.flare.ui.screen.home.DiscoverScreen +import dev.dimension.flare.ui.screen.home.FansScreen +import dev.dimension.flare.ui.screen.home.FollowingScreen import dev.dimension.flare.ui.screen.home.HomeTimelineScreen import dev.dimension.flare.ui.screen.home.NotificationScreen import dev.dimension.flare.ui.screen.home.ProfileScreen @@ -339,8 +341,22 @@ internal fun WindowScope.RouteContent( ), ) }, - onFollowListClick = {}, - onFansListClick = {}, + onFollowListClick = { + navigate( + Route.Following( + accountType = route.accountType, + userKey = it, + ), + ) + }, + onFansListClick = { + navigate( + Route.Fans( + accountType = route.accountType, + userKey = it, + ), + ) + }, ) } @@ -435,8 +451,22 @@ internal fun WindowScope.RouteContent( ), ) }, - onFollowListClick = {}, - onFansListClick = {}, + onFollowListClick = { + navigate( + Route.Following( + accountType = route.accountType, + userKey = it, + ), + ) + }, + onFansListClick = { + navigate( + Route.Fans( + accountType = route.accountType, + userKey = it, + ), + ) + }, ) } @@ -550,5 +580,33 @@ internal fun WindowScope.RouteContent( Route.StorageUsage -> StorageScreen() + + is Route.Following -> + FollowingScreen( + accountType = route.accountType, + userKey = route.userKey, + onUserClick = { + navigate( + Profile( + accountType = route.accountType, + userKey = it, + ), + ) + }, + ) + + is Route.Fans -> + FansScreen( + accountType = route.accountType, + userKey = route.userKey, + onUserClick = { + navigate( + Profile( + accountType = route.accountType, + userKey = it, + ), + ) + }, + ) } } diff --git a/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/home/UserListScreen.kt b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/home/UserListScreen.kt new file mode 100644 index 000000000..08ffa8003 --- /dev/null +++ b/desktopApp/src/main/kotlin/dev/dimension/flare/ui/screen/home/UserListScreen.kt @@ -0,0 +1,115 @@ +package dev.dimension.flare.ui.screen.home + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import dev.dimension.flare.LocalWindowPadding +import dev.dimension.flare.common.PagingState +import dev.dimension.flare.model.AccountType +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.common.itemsIndexed +import dev.dimension.flare.ui.component.AccountItem +import dev.dimension.flare.ui.model.UiState +import dev.dimension.flare.ui.model.UiUserV2 +import dev.dimension.flare.ui.presenter.invoke +import dev.dimension.flare.ui.presenter.profile.FansPresenter +import dev.dimension.flare.ui.presenter.profile.FollowingPresenter +import dev.dimension.flare.ui.presenter.profile.UserListPresenter +import dev.dimension.flare.ui.theme.screenHorizontalPadding +import kotlinx.coroutines.launch +import moe.tlaster.precompose.molecule.producePresenter + +@Composable +internal fun FollowingScreen( + accountType: AccountType, + userKey: MicroBlogKey, + onUserClick: (MicroBlogKey) -> Unit, +) { + val state by producePresenter( + key = "FollowingScreen$userKey", + ) { + val scope = rememberCoroutineScope() + val state = + remember(accountType, userKey) { + FollowingPresenter(accountType, userKey) + }.invoke() + object : UserListPresenter.State by state { + fun refresh() { + scope.launch { + state.refreshSuspend() + } + } + } + } + + UserListScreen( + data = state.listState, + onUserClick = onUserClick, + ) +} + +@Composable +internal fun FansScreen( + accountType: AccountType, + userKey: MicroBlogKey, + onUserClick: (MicroBlogKey) -> Unit, +) { + val state by producePresenter( + key = "FansScreen$userKey", + ) { + val scope = rememberCoroutineScope() + val state = + remember(accountType, userKey) { + FansPresenter(accountType, userKey) + }.invoke() + object : UserListPresenter.State by state { + fun refresh() { + scope.launch { + state.refreshSuspend() + } + } + } + } + + UserListScreen( + data = state.listState, + onUserClick = onUserClick, + ) +} + +@Composable +private fun UserListScreen( + data: PagingState, + onUserClick: (MicroBlogKey) -> Unit, +) { + LazyColumn( + contentPadding = LocalWindowPadding.current, + modifier = + Modifier + .padding(horizontal = screenHorizontalPadding), + verticalArrangement = Arrangement.spacedBy(2.dp), + ) { + itemsIndexed( + data, + loadingContent = { index, itemCount -> + AccountItem( + userState = UiState.Loading(), + onClick = { onUserClick(it) }, + toLogin = {}, + ) + }, + ) { index, itemCount, it -> + AccountItem( + userState = UiState.Success(it), + onClick = { onUserClick(it) }, + toLogin = {}, + ) + } + } +}