feat: add Profile UI settings page
New "Profile UI" settings page lets users toggle which profile-screen sections are loaded and displayed. All toggles default to on. Toggles: - Profile Badges (NIP-58 badges row) - App Recommendations (apps row in profile header) - Zap Received Feed (received zaps tab) - Followers Feed (followers tab) Tabs are filtered from the profile pager so disabled feeds no longer allocate a page or fire their subscriptions.
This commit is contained in:
@@ -48,6 +48,10 @@ data class UiSettings(
|
|||||||
val showHomeNewThreadsTab: Boolean = true,
|
val showHomeNewThreadsTab: Boolean = true,
|
||||||
val showHomeConversationsTab: Boolean = true,
|
val showHomeConversationsTab: Boolean = true,
|
||||||
val showHomeEverythingTab: Boolean = false,
|
val showHomeEverythingTab: Boolean = false,
|
||||||
|
val showProfileBadges: Boolean = true,
|
||||||
|
val showProfileAppRecommendations: Boolean = true,
|
||||||
|
val showProfileZapReceivedFeed: Boolean = true,
|
||||||
|
val showProfileFollowersFeed: Boolean = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class ThemeType(
|
enum class ThemeType(
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ class UiSettingsFlow(
|
|||||||
val showHomeNewThreadsTab: MutableStateFlow<Boolean> = MutableStateFlow(true),
|
val showHomeNewThreadsTab: MutableStateFlow<Boolean> = MutableStateFlow(true),
|
||||||
val showHomeConversationsTab: MutableStateFlow<Boolean> = MutableStateFlow(true),
|
val showHomeConversationsTab: MutableStateFlow<Boolean> = MutableStateFlow(true),
|
||||||
val showHomeEverythingTab: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
val showHomeEverythingTab: MutableStateFlow<Boolean> = MutableStateFlow(false),
|
||||||
|
val showProfileBadges: MutableStateFlow<Boolean> = MutableStateFlow(true),
|
||||||
|
val showProfileAppRecommendations: MutableStateFlow<Boolean> = MutableStateFlow(true),
|
||||||
|
val showProfileZapReceivedFeed: MutableStateFlow<Boolean> = MutableStateFlow(true),
|
||||||
|
val showProfileFollowersFeed: MutableStateFlow<Boolean> = MutableStateFlow(true),
|
||||||
) {
|
) {
|
||||||
val listOfFlows: List<Flow<Any?>> =
|
val listOfFlows: List<Flow<Any?>> =
|
||||||
listOf<Flow<Any?>>(
|
listOf<Flow<Any?>>(
|
||||||
@@ -70,6 +74,10 @@ class UiSettingsFlow(
|
|||||||
showHomeNewThreadsTab,
|
showHomeNewThreadsTab,
|
||||||
showHomeConversationsTab,
|
showHomeConversationsTab,
|
||||||
showHomeEverythingTab,
|
showHomeEverythingTab,
|
||||||
|
showProfileBadges,
|
||||||
|
showProfileAppRecommendations,
|
||||||
|
showProfileZapReceivedFeed,
|
||||||
|
showProfileFollowersFeed,
|
||||||
)
|
)
|
||||||
|
|
||||||
// emits at every change in any of the propertyes.
|
// emits at every change in any of the propertyes.
|
||||||
@@ -96,6 +104,10 @@ class UiSettingsFlow(
|
|||||||
flows[16] as Boolean,
|
flows[16] as Boolean,
|
||||||
flows[17] as Boolean,
|
flows[17] as Boolean,
|
||||||
flows[18] as Boolean,
|
flows[18] as Boolean,
|
||||||
|
flows[19] as Boolean,
|
||||||
|
flows[20] as Boolean,
|
||||||
|
flows[21] as Boolean,
|
||||||
|
flows[22] as Boolean,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +132,10 @@ class UiSettingsFlow(
|
|||||||
showHomeNewThreadsTab.value,
|
showHomeNewThreadsTab.value,
|
||||||
showHomeConversationsTab.value,
|
showHomeConversationsTab.value,
|
||||||
showHomeEverythingTab.value,
|
showHomeEverythingTab.value,
|
||||||
|
showProfileBadges.value,
|
||||||
|
showProfileAppRecommendations.value,
|
||||||
|
showProfileZapReceivedFeed.value,
|
||||||
|
showProfileFollowersFeed.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun update(torSettings: UiSettings): Boolean {
|
fun update(torSettings: UiSettings): Boolean {
|
||||||
@@ -201,6 +217,22 @@ class UiSettingsFlow(
|
|||||||
showHomeEverythingTab.tryEmit(torSettings.showHomeEverythingTab)
|
showHomeEverythingTab.tryEmit(torSettings.showHomeEverythingTab)
|
||||||
any = true
|
any = true
|
||||||
}
|
}
|
||||||
|
if (showProfileBadges.value != torSettings.showProfileBadges) {
|
||||||
|
showProfileBadges.tryEmit(torSettings.showProfileBadges)
|
||||||
|
any = true
|
||||||
|
}
|
||||||
|
if (showProfileAppRecommendations.value != torSettings.showProfileAppRecommendations) {
|
||||||
|
showProfileAppRecommendations.tryEmit(torSettings.showProfileAppRecommendations)
|
||||||
|
any = true
|
||||||
|
}
|
||||||
|
if (showProfileZapReceivedFeed.value != torSettings.showProfileZapReceivedFeed) {
|
||||||
|
showProfileZapReceivedFeed.tryEmit(torSettings.showProfileZapReceivedFeed)
|
||||||
|
any = true
|
||||||
|
}
|
||||||
|
if (showProfileFollowersFeed.value != torSettings.showProfileFollowersFeed) {
|
||||||
|
showProfileFollowersFeed.tryEmit(torSettings.showProfileFollowersFeed)
|
||||||
|
any = true
|
||||||
|
}
|
||||||
|
|
||||||
return any
|
return any
|
||||||
}
|
}
|
||||||
@@ -239,6 +271,10 @@ class UiSettingsFlow(
|
|||||||
MutableStateFlow(uiSettings.showHomeNewThreadsTab),
|
MutableStateFlow(uiSettings.showHomeNewThreadsTab),
|
||||||
MutableStateFlow(uiSettings.showHomeConversationsTab),
|
MutableStateFlow(uiSettings.showHomeConversationsTab),
|
||||||
MutableStateFlow(uiSettings.showHomeEverythingTab),
|
MutableStateFlow(uiSettings.showHomeEverythingTab),
|
||||||
|
MutableStateFlow(uiSettings.showProfileBadges),
|
||||||
|
MutableStateFlow(uiSettings.showProfileAppRecommendations),
|
||||||
|
MutableStateFlow(uiSettings.showProfileZapReceivedFeed),
|
||||||
|
MutableStateFlow(uiSettings.showProfileFollowersFeed),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -112,6 +112,10 @@ class UiSharedPreferences(
|
|||||||
val UI_SHOW_HOME_NEW_THREADS_TAB = booleanPreferencesKey("ui.show_home_new_threads_tab")
|
val UI_SHOW_HOME_NEW_THREADS_TAB = booleanPreferencesKey("ui.show_home_new_threads_tab")
|
||||||
val UI_SHOW_HOME_CONVERSATIONS_TAB = booleanPreferencesKey("ui.show_home_conversations_tab")
|
val UI_SHOW_HOME_CONVERSATIONS_TAB = booleanPreferencesKey("ui.show_home_conversations_tab")
|
||||||
val UI_SHOW_HOME_EVERYTHING_TAB = booleanPreferencesKey("ui.show_home_everything_tab")
|
val UI_SHOW_HOME_EVERYTHING_TAB = booleanPreferencesKey("ui.show_home_everything_tab")
|
||||||
|
val UI_SHOW_PROFILE_BADGES = booleanPreferencesKey("ui.show_profile_badges")
|
||||||
|
val UI_SHOW_PROFILE_APP_RECOMMENDATIONS = booleanPreferencesKey("ui.show_profile_app_recommendations")
|
||||||
|
val UI_SHOW_PROFILE_ZAP_RECEIVED_FEED = booleanPreferencesKey("ui.show_profile_zap_received_feed")
|
||||||
|
val UI_SHOW_PROFILE_FOLLOWERS_FEED = booleanPreferencesKey("ui.show_profile_followers_feed")
|
||||||
|
|
||||||
suspend fun uiPreferences(context: Context): UiSettings? =
|
suspend fun uiPreferences(context: Context): UiSettings? =
|
||||||
try {
|
try {
|
||||||
@@ -142,6 +146,10 @@ class UiSharedPreferences(
|
|||||||
showHomeNewThreadsTab = preferences[UI_SHOW_HOME_NEW_THREADS_TAB] ?: true,
|
showHomeNewThreadsTab = preferences[UI_SHOW_HOME_NEW_THREADS_TAB] ?: true,
|
||||||
showHomeConversationsTab = preferences[UI_SHOW_HOME_CONVERSATIONS_TAB] ?: true,
|
showHomeConversationsTab = preferences[UI_SHOW_HOME_CONVERSATIONS_TAB] ?: true,
|
||||||
showHomeEverythingTab = preferences[UI_SHOW_HOME_EVERYTHING_TAB] ?: false,
|
showHomeEverythingTab = preferences[UI_SHOW_HOME_EVERYTHING_TAB] ?: false,
|
||||||
|
showProfileBadges = preferences[UI_SHOW_PROFILE_BADGES] ?: true,
|
||||||
|
showProfileAppRecommendations = preferences[UI_SHOW_PROFILE_APP_RECOMMENDATIONS] ?: true,
|
||||||
|
showProfileZapReceivedFeed = preferences[UI_SHOW_PROFILE_ZAP_RECEIVED_FEED] ?: true,
|
||||||
|
showProfileFollowersFeed = preferences[UI_SHOW_PROFILE_FOLLOWERS_FEED] ?: true,
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) throw e
|
if (e is CancellationException) throw e
|
||||||
@@ -185,6 +193,10 @@ class UiSharedPreferences(
|
|||||||
preferences[UI_SHOW_HOME_NEW_THREADS_TAB] = sharedSettings.showHomeNewThreadsTab
|
preferences[UI_SHOW_HOME_NEW_THREADS_TAB] = sharedSettings.showHomeNewThreadsTab
|
||||||
preferences[UI_SHOW_HOME_CONVERSATIONS_TAB] = sharedSettings.showHomeConversationsTab
|
preferences[UI_SHOW_HOME_CONVERSATIONS_TAB] = sharedSettings.showHomeConversationsTab
|
||||||
preferences[UI_SHOW_HOME_EVERYTHING_TAB] = sharedSettings.showHomeEverythingTab
|
preferences[UI_SHOW_HOME_EVERYTHING_TAB] = sharedSettings.showHomeEverythingTab
|
||||||
|
preferences[UI_SHOW_PROFILE_BADGES] = sharedSettings.showProfileBadges
|
||||||
|
preferences[UI_SHOW_PROFILE_APP_RECOMMENDATIONS] = sharedSettings.showProfileAppRecommendations
|
||||||
|
preferences[UI_SHOW_PROFILE_ZAP_RECEIVED_FEED] = sharedSettings.showProfileZapReceivedFeed
|
||||||
|
preferences[UI_SHOW_PROFILE_FOLLOWERS_FEED] = sharedSettings.showProfileFollowersFeed
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e is CancellationException) throw e
|
if (e is CancellationException) throw e
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.HomeTabsSettingsSc
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NIP47SetupScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NIP47SetupScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NamecoinSettingsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NamecoinSettingsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.OtsSettingsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.OtsSettingsScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.ProfileUiSettingsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.ReactionsSettingsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.ReactionsSettingsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SecurityFiltersScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SecurityFiltersScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsScreen
|
||||||
@@ -313,6 +314,7 @@ fun BuildNavigation(
|
|||||||
composableFromEnd<Route.ReactionsSettings> { ReactionsSettingsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.ReactionsSettings> { ReactionsSettingsScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.BottomBarSettings> { BottomBarSettingsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.BottomBarSettings> { BottomBarSettingsScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.HomeTabsSettings> { HomeTabsSettingsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.HomeTabsSettings> { HomeTabsSettingsScreen(accountViewModel, nav) }
|
||||||
|
composableFromEnd<Route.ProfileUiSettings> { ProfileUiSettingsScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.VideoPlayerSettings> { VideoPlayerSettingsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.VideoPlayerSettings> { VideoPlayerSettingsScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.CallSettings> { CallSettingsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.CallSettings> { CallSettingsScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.ImportFollowsSelectUser> { ImportFollowListSelectUserScreen(accountViewModel, nav) }
|
composableFromEnd<Route.ImportFollowsSelectUser> { ImportFollowListSelectUserScreen(accountViewModel, nav) }
|
||||||
|
|||||||
@@ -225,6 +225,8 @@ sealed class Route {
|
|||||||
|
|
||||||
@Serializable object HomeTabsSettings : Route()
|
@Serializable object HomeTabsSettings : Route()
|
||||||
|
|
||||||
|
@Serializable object ProfileUiSettings : Route()
|
||||||
|
|
||||||
@Serializable object VideoPlayerSettings : Route()
|
@Serializable object VideoPlayerSettings : Route()
|
||||||
|
|
||||||
@Serializable object CallSettings : Route()
|
@Serializable object CallSettings : Route()
|
||||||
|
|||||||
+69
-32
@@ -59,6 +59,7 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
@@ -439,7 +440,28 @@ private fun RenderScreen(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val pagerState = rememberPagerState { 11 }
|
val ui = accountViewModel.settings.uiSettingsFlow
|
||||||
|
val showFollowersTab by ui.showProfileFollowersFeed.collectAsStateWithLifecycle()
|
||||||
|
val showZapsTab by ui.showProfileZapReceivedFeed.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val visibleTabs =
|
||||||
|
remember(showFollowersTab, showZapsTab) {
|
||||||
|
buildList {
|
||||||
|
add(ProfileTab.Notes)
|
||||||
|
add(ProfileTab.Replies)
|
||||||
|
add(ProfileTab.Mutual)
|
||||||
|
add(ProfileTab.Gallery)
|
||||||
|
add(ProfileTab.Follows)
|
||||||
|
if (showFollowersTab) add(ProfileTab.Followers)
|
||||||
|
if (showZapsTab) add(ProfileTab.Zaps)
|
||||||
|
add(ProfileTab.Bookmarks)
|
||||||
|
add(ProfileTab.FollowedTags)
|
||||||
|
add(ProfileTab.Reports)
|
||||||
|
add(ProfileTab.Relays)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val pagerState = rememberPagerState(pageCount = { visibleTabs.size })
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
ProfileHeader(baseUser, appRecommendations, externalIdentities, nav, accountViewModel)
|
ProfileHeader(baseUser, appRecommendations, externalIdentities, nav, accountViewModel)
|
||||||
@@ -454,6 +476,7 @@ private fun RenderScreen(
|
|||||||
CreateAndRenderTabs(
|
CreateAndRenderTabs(
|
||||||
baseUser,
|
baseUser,
|
||||||
pagerState,
|
pagerState,
|
||||||
|
visibleTabs,
|
||||||
threadsViewModel,
|
threadsViewModel,
|
||||||
repliesViewModel,
|
repliesViewModel,
|
||||||
mutualViewModel,
|
mutualViewModel,
|
||||||
@@ -471,7 +494,7 @@ private fun RenderScreen(
|
|||||||
modifier = pagerModifier,
|
modifier = pagerModifier,
|
||||||
) { page ->
|
) { page ->
|
||||||
CreateAndRenderPages(
|
CreateAndRenderPages(
|
||||||
page,
|
visibleTabs[page],
|
||||||
baseUser,
|
baseUser,
|
||||||
threadsViewModel,
|
threadsViewModel,
|
||||||
repliesViewModel,
|
repliesViewModel,
|
||||||
@@ -490,9 +513,23 @@ private fun RenderScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum class ProfileTab {
|
||||||
|
Notes,
|
||||||
|
Replies,
|
||||||
|
Mutual,
|
||||||
|
Gallery,
|
||||||
|
Follows,
|
||||||
|
Followers,
|
||||||
|
Zaps,
|
||||||
|
Bookmarks,
|
||||||
|
FollowedTags,
|
||||||
|
Reports,
|
||||||
|
Relays,
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CreateAndRenderPages(
|
private fun CreateAndRenderPages(
|
||||||
page: Int,
|
tab: ProfileTab,
|
||||||
baseUser: User,
|
baseUser: User,
|
||||||
threadsViewModel: UserProfileNewThreadsFeedViewModel,
|
threadsViewModel: UserProfileNewThreadsFeedViewModel,
|
||||||
repliesViewModel: UserProfileConversationsFeedViewModel,
|
repliesViewModel: UserProfileConversationsFeedViewModel,
|
||||||
@@ -514,18 +551,18 @@ private fun CreateAndRenderPages(
|
|||||||
accountViewModel,
|
accountViewModel,
|
||||||
)
|
)
|
||||||
|
|
||||||
when (page) {
|
when (tab) {
|
||||||
0 -> TabNotesNewThreads(threadsViewModel, pinnedNotesFeedViewModel, accountViewModel, nav)
|
ProfileTab.Notes -> TabNotesNewThreads(threadsViewModel, pinnedNotesFeedViewModel, accountViewModel, nav)
|
||||||
1 -> TabNotesConversations(repliesViewModel, accountViewModel, nav)
|
ProfileTab.Replies -> TabNotesConversations(repliesViewModel, accountViewModel, nav)
|
||||||
2 -> TabMutualConversations(mutualViewModel, accountViewModel, nav)
|
ProfileTab.Mutual -> TabMutualConversations(mutualViewModel, accountViewModel, nav)
|
||||||
3 -> TabGallery(galleryFeedViewModel, accountViewModel, nav)
|
ProfileTab.Gallery -> TabGallery(galleryFeedViewModel, accountViewModel, nav)
|
||||||
4 -> TabFollows(followsFeedViewModel, accountViewModel, nav)
|
ProfileTab.Follows -> TabFollows(followsFeedViewModel, accountViewModel, nav)
|
||||||
5 -> TabFollowers(followersFeedViewModel, accountViewModel, nav)
|
ProfileTab.Followers -> TabFollowers(followersFeedViewModel, accountViewModel, nav)
|
||||||
6 -> TabReceivedZaps(baseUser, zapFeedViewModel, accountViewModel, nav)
|
ProfileTab.Zaps -> TabReceivedZaps(baseUser, zapFeedViewModel, accountViewModel, nav)
|
||||||
7 -> TabBookmarks(bookmarksFeedViewModel, accountViewModel, nav)
|
ProfileTab.Bookmarks -> TabBookmarks(bookmarksFeedViewModel, accountViewModel, nav)
|
||||||
8 -> TabFollowedTags(baseUser, accountViewModel, nav)
|
ProfileTab.FollowedTags -> TabFollowedTags(baseUser, accountViewModel, nav)
|
||||||
9 -> TabReports(baseUser, reportsFeedViewModel, accountViewModel, nav)
|
ProfileTab.Reports -> TabReports(baseUser, reportsFeedViewModel, accountViewModel, nav)
|
||||||
10 -> TabRelays(baseUser, accountViewModel, nav)
|
ProfileTab.Relays -> TabRelays(baseUser, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,6 +585,7 @@ fun UpdateThreadsAndRepliesWhenBlockUnblock(
|
|||||||
private fun CreateAndRenderTabs(
|
private fun CreateAndRenderTabs(
|
||||||
baseUser: User,
|
baseUser: User,
|
||||||
pagerState: PagerState,
|
pagerState: PagerState,
|
||||||
|
visibleTabs: List<ProfileTab>,
|
||||||
threadsViewModel: UserProfileNewThreadsFeedViewModel,
|
threadsViewModel: UserProfileNewThreadsFeedViewModel,
|
||||||
repliesViewModel: UserProfileConversationsFeedViewModel,
|
repliesViewModel: UserProfileConversationsFeedViewModel,
|
||||||
mutualViewModel: UserProfileMutualFeedViewModel,
|
mutualViewModel: UserProfileMutualFeedViewModel,
|
||||||
@@ -561,26 +599,25 @@ private fun CreateAndRenderTabs(
|
|||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
val tabs =
|
visibleTabs.forEachIndexed { index, tab ->
|
||||||
listOf<@Composable (() -> Unit)?>(
|
|
||||||
{ Text(text = stringRes(R.string.notes)) },
|
|
||||||
{ Text(text = stringRes(R.string.replies)) },
|
|
||||||
{ Text(text = stringRes(R.string.mutual)) },
|
|
||||||
{ Text(text = stringRes(R.string.gallery)) },
|
|
||||||
{ FollowTabHeader(followsFeedViewModel, accountViewModel) },
|
|
||||||
{ FollowersTabHeader(baseUser, followersFeedViewModel, accountViewModel) },
|
|
||||||
{ ZapTabHeader(zapFeedViewModel, accountViewModel) },
|
|
||||||
{ BookmarkTabHeader(baseUser, accountViewModel) },
|
|
||||||
{ FollowedTagsTabHeader(baseUser, accountViewModel) },
|
|
||||||
{ ReportsTabHeader(baseUser, reportsFeedViewModel, accountViewModel) },
|
|
||||||
{ RelaysTabHeader(baseUser, accountViewModel) },
|
|
||||||
)
|
|
||||||
|
|
||||||
tabs.forEachIndexed { index, function ->
|
|
||||||
Tab(
|
Tab(
|
||||||
selected = pagerState.currentPage == index,
|
selected = pagerState.currentPage == index,
|
||||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(index) } },
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(index) } },
|
||||||
text = function,
|
text = {
|
||||||
|
when (tab) {
|
||||||
|
ProfileTab.Notes -> Text(text = stringRes(R.string.notes))
|
||||||
|
ProfileTab.Replies -> Text(text = stringRes(R.string.replies))
|
||||||
|
ProfileTab.Mutual -> Text(text = stringRes(R.string.mutual))
|
||||||
|
ProfileTab.Gallery -> Text(text = stringRes(R.string.gallery))
|
||||||
|
ProfileTab.Follows -> FollowTabHeader(followsFeedViewModel, accountViewModel)
|
||||||
|
ProfileTab.Followers -> FollowersTabHeader(baseUser, followersFeedViewModel, accountViewModel)
|
||||||
|
ProfileTab.Zaps -> ZapTabHeader(zapFeedViewModel, accountViewModel)
|
||||||
|
ProfileTab.Bookmarks -> BookmarkTabHeader(baseUser, accountViewModel)
|
||||||
|
ProfileTab.FollowedTags -> FollowedTagsTabHeader(baseUser, accountViewModel)
|
||||||
|
ProfileTab.Reports -> ReportsTabHeader(baseUser, reportsFeedViewModel, accountViewModel)
|
||||||
|
ProfileTab.Relays -> RelaysTabHeader(baseUser, accountViewModel)
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -113,6 +113,10 @@ fun DrawAdditionalInfo(
|
|||||||
|
|
||||||
val displayName = user.info.bestName()
|
val displayName = user.info.bestName()
|
||||||
|
|
||||||
|
val ui = accountViewModel.settings.uiSettingsFlow
|
||||||
|
val showBadges by ui.showProfileBadges.collectAsStateWithLifecycle()
|
||||||
|
val showAppRecommendations by ui.showProfileAppRecommendations.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = SpacedBy3dp) {
|
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = SpacedBy3dp) {
|
||||||
if (displayName != null) {
|
if (displayName != null) {
|
||||||
Row(
|
Row(
|
||||||
@@ -267,7 +271,9 @@ fun DrawAdditionalInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showBadges) {
|
||||||
DisplayBadges(baseUser, accountViewModel, nav)
|
DisplayBadges(baseUser, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
user.info.about?.let {
|
user.info.about?.let {
|
||||||
Row(
|
Row(
|
||||||
@@ -289,9 +295,11 @@ fun DrawAdditionalInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showAppRecommendations) {
|
||||||
DisplayAppRecommendations(appRecommendations, accountViewModel, nav)
|
DisplayAppRecommendations(appRecommendations, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DisplayLastSeen(
|
fun DisplayLastSeen(
|
||||||
|
|||||||
+6
@@ -259,6 +259,12 @@ fun AllSettingsScreen(
|
|||||||
icon = MaterialSymbols.Home,
|
icon = MaterialSymbols.Home,
|
||||||
onClick = { nav.nav(Route.HomeTabsSettings) },
|
onClick = { nav.nav(Route.HomeTabsSettings) },
|
||||||
)
|
)
|
||||||
|
SettingsDivider()
|
||||||
|
SettingsItem(
|
||||||
|
title = R.string.profile_ui_settings,
|
||||||
|
icon = MaterialSymbols.AccountCircle,
|
||||||
|
onClick = { nav.nav(Route.ProfileUiSettings) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsSection(R.string.danger_zone, isDanger = true) {
|
SettingsSection(R.string.danger_zone, isDanger = true) {
|
||||||
|
|||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun ProfileUiSettingsScreenPreview() {
|
||||||
|
ThemeComparisonRow {
|
||||||
|
ProfileUiSettingsScreen(
|
||||||
|
mockAccountViewModel(),
|
||||||
|
EmptyNav(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProfileUiSettingsScreen(
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopBarWithBackButton(stringRes(id = R.string.profile_ui_settings), nav)
|
||||||
|
},
|
||||||
|
) { padding ->
|
||||||
|
Column(Modifier.padding(padding)) {
|
||||||
|
ProfileUiSettingsContent(accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProfileUiSettingsContent(accountViewModel: AccountViewModel) {
|
||||||
|
val ui = accountViewModel.settings.uiSettingsFlow
|
||||||
|
|
||||||
|
val showBadges by ui.showProfileBadges.collectAsStateWithLifecycle()
|
||||||
|
val showAppRecommendations by ui.showProfileAppRecommendations.collectAsStateWithLifecycle()
|
||||||
|
val showZapReceived by ui.showProfileZapReceivedFeed.collectAsStateWithLifecycle()
|
||||||
|
val showFollowers by ui.showProfileFollowersFeed.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.profile_ui_settings_description),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = Color.Gray,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp, start = Size20dp, end = Size20dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
ProfileUiSwitchRow(
|
||||||
|
title = stringRes(R.string.profile_ui_setting_badges),
|
||||||
|
checked = showBadges,
|
||||||
|
onCheckedChange = { ui.showProfileBadges.tryEmit(it) },
|
||||||
|
)
|
||||||
|
HorizontalDivider(modifier = Modifier.padding(horizontal = Size20dp))
|
||||||
|
|
||||||
|
ProfileUiSwitchRow(
|
||||||
|
title = stringRes(R.string.profile_ui_setting_app_recommendations),
|
||||||
|
checked = showAppRecommendations,
|
||||||
|
onCheckedChange = { ui.showProfileAppRecommendations.tryEmit(it) },
|
||||||
|
)
|
||||||
|
HorizontalDivider(modifier = Modifier.padding(horizontal = Size20dp))
|
||||||
|
|
||||||
|
ProfileUiSwitchRow(
|
||||||
|
title = stringRes(R.string.profile_ui_setting_zap_received_feed),
|
||||||
|
checked = showZapReceived,
|
||||||
|
onCheckedChange = { ui.showProfileZapReceivedFeed.tryEmit(it) },
|
||||||
|
)
|
||||||
|
HorizontalDivider(modifier = Modifier.padding(horizontal = Size20dp))
|
||||||
|
|
||||||
|
ProfileUiSwitchRow(
|
||||||
|
title = stringRes(R.string.profile_ui_setting_followers_feed),
|
||||||
|
checked = showFollowers,
|
||||||
|
onCheckedChange = { ui.showProfileFollowersFeed.tryEmit(it) },
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ProfileUiSwitchRow(
|
||||||
|
title: String,
|
||||||
|
checked: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 12.dp, horizontal = Size20dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
Switch(
|
||||||
|
checked = checked,
|
||||||
|
onCheckedChange = onCheckedChange,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1881,6 +1881,12 @@
|
|||||||
<string name="home_tabs_settings">Home Tabs</string>
|
<string name="home_tabs_settings">Home Tabs</string>
|
||||||
<string name="home_tabs_settings_description">Pick which tabs appear on the Home screen. When only one tab is active the tab bar is hidden.</string>
|
<string name="home_tabs_settings_description">Pick which tabs appear on the Home screen. When only one tab is active the tab bar is hidden.</string>
|
||||||
<string name="home_tab_everything">Everything</string>
|
<string name="home_tab_everything">Everything</string>
|
||||||
|
<string name="profile_ui_settings">Profile UI</string>
|
||||||
|
<string name="profile_ui_settings_description">Pick which sections and feeds appear on user profile screens. All options are enabled by default.</string>
|
||||||
|
<string name="profile_ui_setting_badges">Profile Badges</string>
|
||||||
|
<string name="profile_ui_setting_app_recommendations">App Recommendations</string>
|
||||||
|
<string name="profile_ui_setting_zap_received_feed">Zap Received Feed</string>
|
||||||
|
<string name="profile_ui_setting_followers_feed">Followers Feed</string>
|
||||||
<string name="reactions_settings">Reaction Row</string>
|
<string name="reactions_settings">Reaction Row</string>
|
||||||
<string name="reactions_settings_description">Configure which reaction buttons are shown, their order, and whether to display counters.</string>
|
<string name="reactions_settings_description">Configure which reaction buttons are shown, their order, and whether to display counters.</string>
|
||||||
<string name="reactions_settings_enabled">Enabled</string>
|
<string name="reactions_settings_enabled">Enabled</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user