From 885588db86b98d0f47c5f7fb09013b022df210ad Mon Sep 17 00:00:00 2001 From: Jeffrey Starke Date: Thu, 10 Apr 2025 17:36:09 -0400 Subject: [PATCH] Create new sticker management screen with tabbed interface. Adds a skeleton implementation of `StickerManagementActivityV2`. This new activity is not currently connected to anything, but once complete it will replace `StickerManagementActivity`. --- app/src/main/AndroidManifest.xml | 4 + .../stickers/StickerManagementActivityV2.kt | 212 ++++++++++++++++++ .../stickers/StickerManagementViewModelV2.kt | 41 ++++ app/src/main/res/values/strings.xml | 9 + 4 files changed, 266 insertions(+) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementActivityV2.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModelV2.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4651bfb3c8..97ffa9d1e9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -895,6 +895,10 @@ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:exported="false"/> + + Unit +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun StickerManagementScreen( + uiState: StickerManagementUiState, + onNavigateBack: () -> Unit = {}, + modifier: Modifier = Modifier +) { + Scaffold( + topBar = { TopAppBar(onBackPress = onNavigateBack) } + ) { padding -> + + val pages = listOf( + Page( + title = stringResource(R.string.StickerManagement_available_tab_label), + getContent = { AvailableStickersContent(uiState.availablePacks) } + ), + Page( + title = stringResource(R.string.StickerManagement_installed_tab_label), + getContent = { InstalledStickersContent(uiState.installedPacks) } + ) + ) + + val pagerState = rememberPagerState(pageCount = { pages.size }) + val coroutineScope = rememberCoroutineScope() + + Column( + modifier = modifier.padding(padding) + ) { + SecondaryTabRow( + selectedTabIndex = pagerState.currentPage, + indicator = { + TabRowDefaults.SecondaryIndicator( + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.tabIndicatorOffset(pagerState.currentPage) + ) + } + ) { + repeat(pages.size) { pageIndex -> + PagerTab( + title = pages[pageIndex].title, + selected = pagerState.currentPage == pageIndex, + onClick = { coroutineScope.launch { pagerState.animateScrollToPage(pageIndex) } }, + modifier = Modifier.weight(1f) + ) + } + } + + HorizontalPager( + state = pagerState, + beyondViewportPageCount = 1 + ) { pageIndex -> + pages[pageIndex].getContent() + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun TopAppBar( + onBackPress: () -> Unit +) { + Scaffolds.DefaultTopAppBar( + title = stringResource(R.string.StickerManagementActivity_stickers), + titleContent = { _, title -> Text(text = title, style = MaterialTheme.typography.titleLarge) }, + navigationIconPainter = painterResource(R.drawable.symbol_arrow_start_24), + onNavigationClick = onBackPress + ) +} + +@Composable +private fun PagerTab( + title: String, + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Tab( + text = { + Text( + text = title, + style = MaterialTheme.typography.bodyLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + selected = selected, + onClick = onClick, + modifier = modifier + ) +} + +@Composable +private fun AvailableStickersContent( + packs: List +) { + if (packs.isEmpty()) { + EmptyView(text = stringResource(R.string.StickerManagement_available_tab_empty_text)) + } else { + // TODO show available stickers list + } +} + +@Composable +private fun InstalledStickersContent( + packs: List +) { + if (packs.isEmpty()) { + EmptyView(text = stringResource(R.string.StickerManagement_installed_tab_empty_text)) + } else { + // TODO show installed stickers list + } +} + +@Composable +private fun EmptyView( + text: String +) { + Text( + text = text, + style = MaterialTheme.typography.bodyMedium, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxSize() + .wrapContentHeight(align = Alignment.CenterVertically) + ) +} + +@SignalPreview +@Composable +private fun StickerManagementScreenEmptyStatePreview() { + Previews.Preview { + StickerManagementScreen( + StickerManagementUiState( + availablePacks = emptyList(), + installedPacks = emptyList() + ) + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModelV2.kt b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModelV2.kt new file mode 100644 index 0000000000..2c636b6e21 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/StickerManagementViewModelV2.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.stickers + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import org.thoughtcrime.securesms.database.model.StickerPackRecord + +class StickerManagementViewModelV2 : ViewModel() { + private val _uiState = MutableStateFlow(StickerManagementUiState()) + val uiState: StateFlow = _uiState.asStateFlow() +} + +data class StickerManagementUiState( + val availablePacks: List = emptyList(), + val installedPacks: List = emptyList(), + val isMultiSelectMode: Boolean = false +) + +data class AvailableStickerPack( + val record: StickerPackRecord, + val isBlessed: Boolean, + val downloadStatus: DownloadStatus +) { + sealed class DownloadStatus { + data object NotDownloaded : DownloadStatus() + data class InProgress(val progressPercent: Double) : DownloadStatus() + data object Downloaded : DownloadStatus() + } +} + +data class InstalledStickerPack( + private val record: StickerPackRecord, + val sortOrder: Int, + val isSelected: Boolean +) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f7a2ecba67..6957e9b6d9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2781,6 +2781,15 @@ Stickers + + Available + + Installed + + No sticker packs are available + + No sticker packs are installed + Installed Stickers Stickers You Received