Move StickerManagementScreen to its own file.

This commit is contained in:
jeffrey-signal
2026-01-07 11:22:18 -05:00
committed by Michelle Tang
parent 3d8f364d59
commit 2f0a63774f
13 changed files with 167 additions and 136 deletions

View File

@@ -620,7 +620,7 @@
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".stickers.StickerManagementActivity"
android:name=".stickers.manage.StickerManagementActivity"
android:exported="false"
android:theme="@style/Signal.DayNight.NoActionBar" />

View File

@@ -321,9 +321,9 @@ import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
import org.thoughtcrime.securesms.sms.MessageSender
import org.thoughtcrime.securesms.stickers.StickerEventListener
import org.thoughtcrime.securesms.stickers.StickerLocator
import org.thoughtcrime.securesms.stickers.StickerManagementScreen
import org.thoughtcrime.securesms.stickers.StickerPackInstallEvent
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity
import org.thoughtcrime.securesms.stickers.manage.StickerManagementScreen
import org.thoughtcrime.securesms.stories.StoryViewerArgs
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
import org.thoughtcrime.securesms.util.BottomSheetUtil

View File

@@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.keyboard.sticker.StickerSearchDialogFragment;
import org.thoughtcrime.securesms.scribbles.stickers.FeatureSticker;
import org.thoughtcrime.securesms.scribbles.stickers.ScribbleStickersFragment;
import org.thoughtcrime.securesms.stickers.StickerEventListener;
import org.thoughtcrime.securesms.stickers.StickerManagementScreen;
import org.thoughtcrime.securesms.stickers.manage.StickerManagementScreen;
import org.thoughtcrime.securesms.util.ViewUtil;
public final class ImageEditorStickerSelectActivity extends AppCompatActivity implements StickerEventListener, MediaKeyboard.MediaKeyboardListener, StickerKeyboardPageFragment.Callback, ScribbleStickersFragment.Callback {

View File

@@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.glide.cache.ApngOptions;
import org.thoughtcrime.securesms.mms.DecryptableUri;
import org.thoughtcrime.securesms.sharing.MultiShareArgs;
import org.thoughtcrime.securesms.stickers.StickerManifest.Sticker;
import org.thoughtcrime.securesms.stickers.manage.StickerManagementRepository;
import org.thoughtcrime.securesms.util.DeviceProperties;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;

View File

@@ -12,6 +12,7 @@ import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.database.DatabaseObserver;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.stickers.StickerPackPreviewRepository.StickerManifestResult;
import org.thoughtcrime.securesms.stickers.manage.StickerManagementRepository;
import java.util.Optional;

View File

@@ -15,6 +15,7 @@ import kotlinx.coroutines.launch
import org.signal.core.util.orNull
import org.thoughtcrime.securesms.database.model.StickerPackParams
import org.thoughtcrime.securesms.stickers.StickerPackPreviewUiState.ContentState
import org.thoughtcrime.securesms.stickers.manage.StickerManagementRepository
import kotlin.jvm.optionals.getOrElse
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

View File

@@ -7,6 +7,8 @@ package org.thoughtcrime.securesms.stickers
import org.thoughtcrime.securesms.database.model.StickerPackRecord
import org.thoughtcrime.securesms.database.model.StickerRecord
import org.thoughtcrime.securesms.stickers.manage.AvailableStickerPack
import org.thoughtcrime.securesms.stickers.manage.InstalledStickerPack
import java.util.UUID
/**

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.stickers.manage
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch
import org.signal.core.ui.compose.copied.androidx.compose.DragAndDropEvent
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.SignalTheme
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
import org.thoughtcrime.securesms.database.model.StickerPackId
import org.thoughtcrime.securesms.database.model.StickerPackKey
import org.thoughtcrime.securesms.sharing.MultiShareArgs
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity
import org.thoughtcrime.securesms.stickers.StickerUrl
import org.thoughtcrime.securesms.util.viewModel
/**
* Activity implementation of [StickerManagementScreen].
*/
class StickerManagementActivity : PassphraseRequiredActivity() {
companion object {
@JvmStatic
fun createIntent(context: Context): Intent = Intent(context, StickerManagementActivity::class.java)
}
private val viewModel by viewModel { StickerManagementViewModel() }
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onScreenVisible()
}
}
setContent {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
SignalTheme {
StickerManagementScreen(
uiState = uiState,
onNavigateBack = ::supportFinishAfterTransition,
onSetMultiSelectModeEnabled = viewModel::setMultiSelectEnabled,
onSnackbarDismiss = viewModel::onSnackbarDismiss,
availableTabCallbacks = remember {
object : AvailableStickersContentCallbacks {
override fun onForwardClick(pack: AvailableStickerPack) = openShareSheet(pack.id, pack.key)
override fun onInstallClick(pack: AvailableStickerPack) = viewModel.installStickerPack(pack)
override fun onShowPreviewClick(pack: AvailableStickerPack) = navigateToStickerPreview(pack.id, pack.key)
}
},
installedTabCallbacks = remember {
object : InstalledStickersContentCallbacks {
override fun onForwardClick(pack: InstalledStickerPack) = openShareSheet(pack.id, pack.key)
override fun onRemoveClick(packIds: Set<StickerPackId>) = viewModel.onUninstallStickerPacksRequested(packIds)
override fun onRemoveStickerPacksConfirmed(packIds: Set<StickerPackId>) = viewModel.onUninstallStickerPacksConfirmed(packIds)
override fun onRemoveStickerPacksCanceled() = viewModel.onUninstallStickerPacksCanceled()
override fun onSelectionToggle(pack: InstalledStickerPack) = viewModel.toggleSelection(pack)
override fun onSelectAllToggle() = viewModel.toggleSelectAll()
override fun onDragAndDropEvent(event: DragAndDropEvent) {
when (event) {
is DragAndDropEvent.OnItemMove -> viewModel.updatePosition(event.fromIndex, event.toIndex)
is DragAndDropEvent.OnItemDrop -> viewModel.saveInstalledPacksSortOrder()
is DragAndDropEvent.OnDragCancel -> {}
}
}
override fun onShowPreviewClick(pack: InstalledStickerPack) = navigateToStickerPreview(pack.id, pack.key)
}
}
)
}
}
}
private fun openShareSheet(packId: StickerPackId, packKey: StickerPackKey) {
MultiselectForwardFragment.showBottomSheet(
supportFragmentManager = supportFragmentManager,
multiselectForwardFragmentArgs = MultiselectForwardFragmentArgs(
multiShareArgs = listOf(
MultiShareArgs.Builder()
.withDraftText(StickerUrl.createShareLink(packId.value, packKey.value))
.build()
),
title = R.string.StickerManagement_share_sheet_title
)
)
}
private fun navigateToStickerPreview(packId: StickerPackId, packKey: StickerPackKey) {
startActivity(StickerPackPreviewActivity.getIntent(packId.value, packKey.value))
}
}

View File

@@ -1,9 +1,9 @@
/*
* Copyright 2025 Signal Messenger, LLC
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.stickers
package org.thoughtcrime.securesms.stickers.manage
import android.app.Dialog
import android.os.Bundle
@@ -36,6 +36,8 @@ import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectFor
import org.thoughtcrime.securesms.database.model.StickerPackId
import org.thoughtcrime.securesms.database.model.StickerPackKey
import org.thoughtcrime.securesms.sharing.MultiShareArgs
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity
import org.thoughtcrime.securesms.stickers.StickerUrl
import org.thoughtcrime.securesms.util.viewModel
/**

View File

@@ -1,9 +1,9 @@
/*
* Copyright 2025 Signal Messenger, LLC
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.stickers
package org.thoughtcrime.securesms.stickers.manage
import androidx.annotation.Discouraged
import kotlinx.coroutines.CoroutineScope
@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.jobmanager.JobManager
import org.thoughtcrime.securesms.jobs.MultiDeviceStickerPackOperationJob
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.stickers.BlessedPacks
/**
* Handles the retrieval and modification of sticker pack data.

View File

@@ -1,16 +1,12 @@
/*
* Copyright 2025 Signal Messenger, LLC
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.stickers
package org.thoughtcrime.securesms.stickers.manage
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.os.Bundle
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
@@ -63,10 +59,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.window.core.layout.WindowSizeClass
import kotlinx.coroutines.launch
import org.signal.core.ui.compose.DayNightPreviews
@@ -81,99 +73,45 @@ import org.signal.core.ui.compose.copied.androidx.compose.DraggableItem
import org.signal.core.ui.compose.copied.androidx.compose.dragContainer
import org.signal.core.ui.compose.copied.androidx.compose.rememberDragDropState
import org.signal.core.ui.compose.showSnackbar
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.menu.ActionItem
import org.thoughtcrime.securesms.components.menu.SignalBottomActionBar
import org.thoughtcrime.securesms.compose.SignalTheme
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragment
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
import org.thoughtcrime.securesms.database.model.StickerPackId
import org.thoughtcrime.securesms.database.model.StickerPackKey
import org.thoughtcrime.securesms.sharing.MultiShareArgs
import org.thoughtcrime.securesms.stickers.AvailableStickerPack.DownloadStatus
import org.thoughtcrime.securesms.stickers.StickerManagementActivity.Companion.createIntent
import org.thoughtcrime.securesms.util.viewModel
import org.thoughtcrime.securesms.stickers.StickerPreviewDataFactory
import org.thoughtcrime.securesms.stickers.manage.AvailableStickerPack.DownloadStatus
import org.thoughtcrime.securesms.window.getWindowSizeClass
import java.text.NumberFormat
/**
* Displays all the available and installed sticker packs, enabling installation, uninstallation, and sorting.
*/
class StickerManagementActivity : PassphraseRequiredActivity() {
companion object {
@JvmStatic
fun createIntent(context: Context): Intent = Intent(context, StickerManagementActivity::class.java)
}
private val viewModel by viewModel { StickerManagementViewModel() }
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onScreenVisible()
}
}
setContent {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
SignalTheme {
StickerManagementScreen(
uiState = uiState,
onNavigateBack = ::supportFinishAfterTransition,
onSetMultiSelectModeEnabled = viewModel::setMultiSelectEnabled,
onSnackbarDismiss = viewModel::onSnackbarDismiss,
availableTabCallbacks = remember {
object : AvailableStickersContentCallbacks {
override fun onForwardClick(pack: AvailableStickerPack) = openShareSheet(pack.id, pack.key)
override fun onInstallClick(pack: AvailableStickerPack) = viewModel.installStickerPack(pack)
override fun onShowPreviewClick(pack: AvailableStickerPack) = navigateToStickerPreview(pack.id, pack.key)
}
},
installedTabCallbacks = remember {
object : InstalledStickersContentCallbacks {
override fun onForwardClick(pack: InstalledStickerPack) = openShareSheet(pack.id, pack.key)
override fun onRemoveClick(packIds: Set<StickerPackId>) = viewModel.onUninstallStickerPacksRequested(packIds)
override fun onRemoveStickerPacksConfirmed(packIds: Set<StickerPackId>) = viewModel.onUninstallStickerPacksConfirmed(packIds)
override fun onRemoveStickerPacksCanceled() = viewModel.onUninstallStickerPacksCanceled()
override fun onSelectionToggle(pack: InstalledStickerPack) = viewModel.toggleSelection(pack)
override fun onSelectAllToggle() = viewModel.toggleSelectAll()
override fun onDragAndDropEvent(event: DragAndDropEvent) {
when (event) {
is DragAndDropEvent.OnItemMove -> viewModel.updatePosition(event.fromIndex, event.toIndex)
is DragAndDropEvent.OnItemDrop -> viewModel.saveInstalledPacksSortOrder()
is DragAndDropEvent.OnDragCancel -> {}
}
}
override fun onShowPreviewClick(pack: InstalledStickerPack) = navigateToStickerPreview(pack.id, pack.key)
}
}
)
}
object StickerManagementScreen {
/**
* Shows the screen as a bottom sheet on large devices (tablets/foldables), activity on phones.
*/
@JvmStatic
fun show(activity: FragmentActivity) {
if (showAsBottomSheet(activity.resources)) {
StickerManagementBottomSheet.show(activity.supportFragmentManager)
} else {
activity.startActivity(StickerManagementActivity.createIntent(activity))
}
}
private fun openShareSheet(packId: StickerPackId, packKey: StickerPackKey) {
MultiselectForwardFragment.showBottomSheet(
supportFragmentManager = supportFragmentManager,
multiselectForwardFragmentArgs = MultiselectForwardFragmentArgs(
multiShareArgs = listOf(
MultiShareArgs.Builder()
.withDraftText(StickerUrl.createShareLink(packId.value, packKey.value))
.build()
),
title = R.string.StickerManagement_share_sheet_title
)
/**
* Shows the screen as a bottom sheet on large devices (tablets/foldables), activity on phones.
*/
fun show(fragment: Fragment) {
if (showAsBottomSheet(fragment.resources)) {
StickerManagementBottomSheet.show(fragment.parentFragmentManager)
} else {
fragment.startActivity(StickerManagementActivity.createIntent(fragment.requireContext()))
}
}
private fun showAsBottomSheet(resources: Resources): Boolean {
return resources.getWindowSizeClass().isAtLeastBreakpoint(
widthDpBreakpoint = WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND,
heightDpBreakpoint = WindowSizeClass.HEIGHT_DP_MEDIUM_LOWER_BOUND
)
}
private fun navigateToStickerPreview(packId: StickerPackId, packKey: StickerPackKey) {
startActivity(StickerPackPreviewActivity.getIntent(packId.value, packKey.value))
}
}
private data class Page(
@@ -215,38 +153,12 @@ interface InstalledStickersContentCallbacks {
}
}
object StickerManagementScreen {
/**
* Shows the screen as a bottom sheet on large devices (tablets/foldables), activity on phones.
*/
@JvmStatic
fun show(activity: FragmentActivity) {
if (showAsBottomSheet(activity.resources)) {
StickerManagementBottomSheet.show(activity.supportFragmentManager)
} else {
activity.startActivity(createIntent(activity))
}
}
/**
* Shows the screen as a bottom sheet on large devices (tablets/foldables), activity on phones.
*/
fun show(fragment: Fragment) {
if (showAsBottomSheet(fragment.resources)) {
StickerManagementBottomSheet.show(fragment.parentFragmentManager)
} else {
fragment.startActivity(createIntent(fragment.requireContext()))
}
}
private fun showAsBottomSheet(resources: Resources): Boolean {
return resources.getWindowSizeClass().isAtLeastBreakpoint(
widthDpBreakpoint = WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND,
heightDpBreakpoint = WindowSizeClass.HEIGHT_DP_MEDIUM_LOWER_BOUND
)
}
}
/**
* Displays all the available and installed sticker packs, enabling installation, uninstallation, and sorting.
*
* @see StickerManagementActivity
* @see StickerManagementBottomSheet
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun StickerManagementScreen(

View File

@@ -1,9 +1,9 @@
/*
* Copyright 2025 Signal Messenger, LLC
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.stickers
package org.thoughtcrime.securesms.stickers.manage
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@@ -19,7 +19,8 @@ import org.signal.core.util.swap
import org.thoughtcrime.securesms.database.model.StickerPackId
import org.thoughtcrime.securesms.database.model.StickerPackKey
import org.thoughtcrime.securesms.database.model.StickerPackRecord
import org.thoughtcrime.securesms.stickers.AvailableStickerPack.DownloadStatus
import org.thoughtcrime.securesms.stickers.BlessedPacks
import org.thoughtcrime.securesms.stickers.manage.AvailableStickerPack.DownloadStatus
class StickerManagementViewModel : ViewModel() {
private val stickerManagementRepo = StickerManagementRepository

View File

@@ -1,9 +1,9 @@
/*
* Copyright 2025 Signal Messenger, LLC
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.stickers
package org.thoughtcrime.securesms.stickers.manage
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandHorizontally
@@ -42,7 +42,8 @@ import org.thoughtcrime.securesms.components.transfercontrols.TransferProgressIn
import org.thoughtcrime.securesms.components.transfercontrols.TransferProgressState
import org.thoughtcrime.securesms.compose.GlideImage
import org.thoughtcrime.securesms.mms.DecryptableUri
import org.thoughtcrime.securesms.stickers.AvailableStickerPack.DownloadStatus
import org.thoughtcrime.securesms.stickers.StickerPreviewDataFactory
import org.thoughtcrime.securesms.stickers.manage.AvailableStickerPack.DownloadStatus
import org.thoughtcrime.securesms.util.DeviceProperties
@Composable