From 9d9a72bc70be0838f144d5d51556397411a8ef1c Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 13 Aug 2025 18:00:56 +0900 Subject: [PATCH] add mixed timeline when new account added --- .../ui/screen/home/HomeTimelineScreen.kt | 102 ++++++++++++++---- .../dimension/flare/data/model/TabSettings.kt | 14 ++- .../data/repository/AccountRepository.kt | 20 ++++ .../ui/presenter/home/TimelinePresenter.kt | 2 +- .../settings/AccountEventPresenter.kt | 30 ++++++ .../flare/ui/component/status/AdaptiveCard.kt | 2 +- 6 files changed, 143 insertions(+), 27 deletions(-) create mode 100644 shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AccountEventPresenter.kt diff --git a/app/src/main/java/dev/dimension/flare/ui/screen/home/HomeTimelineScreen.kt b/app/src/main/java/dev/dimension/flare/ui/screen/home/HomeTimelineScreen.kt index 40fcc0134..23560480d 100644 --- a/app/src/main/java/dev/dimension/flare/ui/screen/home/HomeTimelineScreen.kt +++ b/app/src/main/java/dev/dimension/flare/ui/screen/home/HomeTimelineScreen.kt @@ -51,7 +51,7 @@ import androidx.compose.ui.unit.dp import compose.icons.FontAwesomeIcons import compose.icons.fontawesomeicons.Solid import compose.icons.fontawesomeicons.solid.AnglesUp -import compose.icons.fontawesomeicons.solid.Sliders +import compose.icons.fontawesomeicons.solid.Plus import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.rememberHazeState import dev.dimension.flare.R @@ -75,6 +75,7 @@ import dev.dimension.flare.ui.component.platform.isBigScreen import dev.dimension.flare.ui.component.status.AdaptiveCard import dev.dimension.flare.ui.component.status.LazyStatusVerticalStaggeredGrid import dev.dimension.flare.ui.component.status.status +import dev.dimension.flare.ui.model.UiRssSource import dev.dimension.flare.ui.model.UiTimeline import dev.dimension.flare.ui.model.collectAsUiState import dev.dimension.flare.ui.model.map @@ -84,6 +85,7 @@ import dev.dimension.flare.ui.presenter.home.NotificationBadgePresenter import dev.dimension.flare.ui.presenter.home.UserPresenter import dev.dimension.flare.ui.presenter.home.UserState import dev.dimension.flare.ui.presenter.invoke +import dev.dimension.flare.ui.presenter.settings.AccountEventPresenter import dev.dimension.flare.ui.screen.settings.TabIcon import dev.dimension.flare.ui.screen.settings.TabTitle import dev.dimension.flare.ui.theme.screenHorizontalPadding @@ -138,20 +140,29 @@ internal fun HomeTimelineScreen( title = { state.pagerState.onSuccess { pagerState -> state.tabState.onSuccess { tabs -> - if (tabs.size > 1) { + if (tabs.any()) { SecondaryScrollableTabRow( containerColor = Color.Transparent, modifier = Modifier .fillMaxWidth(), - selectedTabIndex = minOf(pagerState.currentPage, tabs.lastIndex), + selectedTabIndex = + minOf( + pagerState.currentPage, + tabs.lastIndex, + ), edgePadding = 0.dp, divider = {}, indicator = { TabRowIndicator( - selectedIndex = minOf(pagerState.currentPage, tabs.lastIndex), + selectedIndex = + minOf( + pagerState.currentPage, + tabs.lastIndex, + ), ) }, + minTabWidth = 48.dp, ) { state.tabState.onSuccess { tabs -> tabs.forEachIndexed { index, tab -> @@ -186,9 +197,17 @@ internal fun HomeTimelineScreen( ) } } + IconButton( + onClick = { + toTabSettings.invoke() + }, + ) { + FAIcon( + imageVector = FontAwesomeIcons.Solid.Plus, + contentDescription = null, + ) + } } - } else { - TabTitle(title = tabs[0].timelineTabItem.metaData.title) } } } @@ -217,14 +236,6 @@ internal fun HomeTimelineScreen( Text(text = stringResource(id = R.string.login_button)) } }.onSuccess { - IconButton( - onClick = toTabSettings, - ) { - FAIcon( - FontAwesomeIcons.Solid.Sliders, - contentDescription = null, - ) - } } }, ) @@ -373,6 +384,43 @@ private fun timelinePresenter( ) }.invoke() + val accountEvent = + remember { + AccountEventPresenter() + }.invoke() + + LaunchedEffect(accountEvent.onAdded) { + accountEvent.onAdded.collect { account -> + val tab = + HomeTimelineTabItem( + accountKey = account.accountKey, + icon = UiRssSource.favIconUrl(account.accountKey.host), + title = + account.accountKey.host + .substringBeforeLast('.') + .substringAfter('.'), + ) + settingsRepository.updateTabSettings { + copy( + mainTabs = + (mainTabs + tab).distinctBy { + it.key + }, + ) + } + } + } + + LaunchedEffect(accountEvent.onRemoved) { + accountEvent.onRemoved.collect { accountKey -> + settingsRepository.updateTabSettings { + copy( + mainTabs = mainTabs.filterNot { it.account == AccountType.Specific(accountKey) }, + ) + } + } + } + val tabs by remember { settingsRepository.tabSettings .map { settings -> @@ -381,15 +429,23 @@ private fun timelinePresenter( HomeTimelineTabItem(AccountType.Guest), ) } else { - listOfNotNull( - if (settings.enableMixedTimeline && settings.mainTabs.size > 1) { - MixedTimelineTabItem( - subTimelineTabItem = settings.mainTabs, - ) - } else { - null - }, - ) + settings.mainTabs + ( + listOfNotNull( + if (settings.enableMixedTimeline && settings.mainTabs.size > 1) { + MixedTimelineTabItem( + subTimelineTabItem = settings.mainTabs, + ) + } else { + null + }, + ) + settings.mainTabs + ).ifEmpty { + listOf( + HomeTimelineTabItem( + accountType = AccountType.Active, + ), + ) + } } }.map { it.toImmutableList() diff --git a/shared/src/androidJvmMain/kotlin/dev/dimension/flare/data/model/TabSettings.kt b/shared/src/androidJvmMain/kotlin/dev/dimension/flare/data/model/TabSettings.kt index 355e9c27c..c0f156502 100644 --- a/shared/src/androidJvmMain/kotlin/dev/dimension/flare/data/model/TabSettings.kt +++ b/shared/src/androidJvmMain/kotlin/dev/dimension/flare/data/model/TabSettings.kt @@ -27,8 +27,8 @@ import java.io.OutputStream @Serializable public data class TabSettings( val secondaryItems: List? = null, - val enableMixedTimeline: Boolean = false, - val mainTabs: List = listOf(HomeTimelineTabItem(AccountType.Active)), + val enableMixedTimeline: Boolean = true, + val mainTabs: List = listOf(), ) @Serializable @@ -630,6 +630,16 @@ public data class HomeTimelineTabItem( icon = IconType.Material(IconType.Material.MaterialIcon.Home), ), ) + + public constructor(accountKey: MicroBlogKey, icon: String, title: String) : + this( + account = AccountType.Specific(accountKey), + metaData = + TabMetaData( + title = TitleType.Text(title), + icon = IconType.Url(icon), + ), + ) } @Serializable diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountRepository.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountRepository.kt index c590f0221..0ee848941 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountRepository.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/repository/AccountRepository.kt @@ -26,6 +26,7 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -59,6 +60,23 @@ internal class AccountRepository( } } + private val _onAdded by lazy { + MutableStateFlow(null) + } + val onAdded: Flow by lazy { + _onAdded + .mapNotNull { it } + .distinctUntilChangedBy { it.accountKey } + } + private val _onRemoved by lazy { + MutableStateFlow(null) + } + val onRemoved: Flow by lazy { + _onRemoved + .mapNotNull { it } + .distinctUntilChangedBy { it } + } + fun addAccount( account: UiAccount, credential: UiAccount.Credential, @@ -71,6 +89,7 @@ internal class AccountRepository( credential_json = credential.encodeJson(), ), ) + _onAdded.value = account } fun setActiveAccount(accountKey: MicroBlogKey) = @@ -83,6 +102,7 @@ internal class AccountRepository( fun delete(accountKey: MicroBlogKey) = coroutineScope.launch { + _onRemoved.value = accountKey cacheDatabase.pagingTimelineDao().deleteByAccountType( AccountType.Specific(accountKey), ) diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenter.kt index 3adf4c0db..6c68addec 100644 --- a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenter.kt +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/home/TimelinePresenter.kt @@ -125,7 +125,7 @@ public abstract class TimelinePresenter : when (data.timeline.accountType) { AccountType.Guest -> null is AccountType.Specific -> { - accounts.first { + accounts.firstOrNull { it.accountKey == data.timeline.accountType.accountKey } } diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AccountEventPresenter.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AccountEventPresenter.kt new file mode 100644 index 000000000..bb4b18d81 --- /dev/null +++ b/shared/src/commonMain/kotlin/dev/dimension/flare/ui/presenter/settings/AccountEventPresenter.kt @@ -0,0 +1,30 @@ +package dev.dimension.flare.ui.presenter.settings + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import dev.dimension.flare.data.repository.AccountRepository +import dev.dimension.flare.model.MicroBlogKey +import dev.dimension.flare.ui.model.UiAccount +import dev.dimension.flare.ui.presenter.PresenterBase +import kotlinx.coroutines.flow.Flow +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +public class AccountEventPresenter : + PresenterBase(), + KoinComponent { + @Immutable + public interface State { + public val onAdded: Flow + public val onRemoved: Flow + } + + private val accountRepository: AccountRepository by inject() + + @Composable + override fun body(): State = + object : State { + override val onAdded = accountRepository.onAdded + override val onRemoved = accountRepository.onRemoved + } +} diff --git a/shared/ui/component/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/AdaptiveCard.kt b/shared/ui/component/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/AdaptiveCard.kt index 6200b1d09..5d5c3352a 100644 --- a/shared/ui/component/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/AdaptiveCard.kt +++ b/shared/ui/component/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/AdaptiveCard.kt @@ -27,7 +27,7 @@ public fun AdaptiveCard( horizontal = 2.dp, vertical = 6.dp, ), - elevated = true, + elevated = false, containerColor = PlatformTheme.colorScheme.card, ) { content.invoke()