diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/BackupUpsellBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/BackupUpsellBottomSheet.kt index 6aef4b2098..36438b72df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/BackupUpsellBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/BackupUpsellBottomSheet.kt @@ -205,7 +205,7 @@ private fun FeatureBullet(text: String) { modifier = Modifier.padding(vertical = 2.dp) ) { Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.symbol_check_24), + imageVector = ImageVector.vectorResource(id = CoreUiR.drawable.symbol_check_24), contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp) diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt index 5d94993742..d024eb76bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/v2/ui/subscription/MessageBackupsFlowViewModel.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.withContext import org.signal.core.util.billing.BillingPurchaseResult import org.signal.core.util.concurrent.SignalDispatchers import org.signal.core.util.logging.Log +import org.signal.core.util.next import org.signal.donations.InAppPaymentType import org.thoughtcrime.securesms.backup.DeletionState import org.thoughtcrime.securesms.backup.v2.BackupRepository @@ -45,7 +46,6 @@ import org.thoughtcrime.securesms.jobs.InAppPaymentPurchaseTokenJob import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.storage.StorageSyncHelper -import org.thoughtcrime.securesms.util.next import org.whispersystems.signalservice.api.storage.IAPSubscriptionId import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration import kotlin.time.Duration.Companion.seconds diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/quality/CallQualityScreens.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/quality/CallQualityScreens.kt index 95a664a8b0..9816068e40 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/quality/CallQualityScreens.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/quality/CallQualityScreens.kt @@ -72,6 +72,7 @@ import org.signal.core.ui.compose.Previews import org.signal.core.ui.compose.Rows import org.signal.core.ui.compose.horizontalGutters import org.thoughtcrime.securesms.R +import org.signal.core.ui.R as CoreUiR @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -413,7 +414,7 @@ private fun IssueChip( leadingIcon = { Icon( imageVector = if (isSelected) { - ImageVector.vectorResource(R.drawable.symbol_check_24) + ImageVector.vectorResource(CoreUiR.drawable.symbol_check_24) } else { ImageVector.vectorResource(issue.category.icon) }, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java index 8b5ce13701..023afe56f4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationReactionOverlay.java @@ -704,7 +704,7 @@ public final class ConversationReactionOverlay extends FrameLayout { } if (menuState.shouldShowSaveAttachmentAction()) { - items.add(new ActionItem(R.drawable.symbol_save_android_24, getResources().getString(R.string.conversation_selection__menu_save), () -> handleActionItemClicked(Action.DOWNLOAD))); + items.add(new ActionItem(org.signal.core.ui.R.drawable.symbol_save_android_24, getResources().getString(R.string.conversation_selection__menu_save), () -> handleActionItemClicked(Action.DOWNLOAD))); } if (menuState.shouldShowCopyAction()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/PinnedContextMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/PinnedContextMenu.kt index 0dbbc41b3c..c5d6cb198b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/PinnedContextMenu.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/PinnedContextMenu.kt @@ -93,7 +93,7 @@ object PinnedContextMenu { message.slideDeck.getStickerSlide() == null ) { add( - ActionItem(R.drawable.symbol_save_android_24, context.getString(R.string.conversation_selection__menu_save)) { + ActionItem(CoreUiR.drawable.symbol_save_android_24, context.getString(R.string.conversation_selection__menu_save)) { callbacks.onSave() } ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeAdapter.java index 2faa2558b0..cd24fbde86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeAdapter.java @@ -64,7 +64,7 @@ final class SafetyNumberChangeAdapter extends ListAdapter { + new ActionItem(org.signal.core.ui.R.drawable.symbol_save_android_24, getResources().getQuantityString(R.plurals.MediaOverviewActivity_save_plural, selectionCount), () -> { Collection selected = getListAdapter().getSelectedMedia(); if (SignalStore.backup().getOptimizeStorage()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt index b016a7e921..31f6f0aa96 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt @@ -419,8 +419,8 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul } val icon = when (state.quality) { - SentMediaQuality.HIGH -> R.drawable.symbol_quality_high_24 - else -> R.drawable.symbol_quality_high_slash_24 + SentMediaQuality.HIGH -> CoreUiR.drawable.symbol_quality_high_24 + else -> CoreUiR.drawable.symbol_quality_high_slash_24 } MediaReviewToastPopupWindow.show(controls, icon, description) @@ -494,8 +494,8 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul } qualityButton.setImageResource( when (state.quality) { - SentMediaQuality.STANDARD -> R.drawable.symbol_quality_high_slash_24 - SentMediaQuality.HIGH -> R.drawable.symbol_quality_high_24 + SentMediaQuality.STANDARD -> CoreUiR.drawable.symbol_quality_high_slash_24 + SentMediaQuality.HIGH -> CoreUiR.drawable.symbol_quality_high_24 } ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextAlignmentButton.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextAlignmentButton.kt index e76056c2fe..117e56de96 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextAlignmentButton.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextAlignmentButton.kt @@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.mediasend.v2.text import android.content.Context import android.util.AttributeSet import androidx.appcompat.widget.AppCompatImageView -import org.thoughtcrime.securesms.util.next +import org.signal.core.util.next typealias OnTextAlignmentChanged = (TextAlignment) -> Unit diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextColorStyleButton.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextColorStyleButton.kt index ef3df393cb..be5ef2d546 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextColorStyleButton.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextColorStyleButton.kt @@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.mediasend.v2.text import android.content.Context import android.util.AttributeSet import androidx.appcompat.widget.AppCompatImageView -import org.thoughtcrime.securesms.util.next +import org.signal.core.util.next typealias OnTextColorStyleChanged = (TextColorStyle) -> Unit diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextFontButton.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextFontButton.kt index cfd60bb9a1..77d0227ba6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextFontButton.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextFontButton.kt @@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.mediasend.v2.text import android.content.Context import android.util.AttributeSet import androidx.appcompat.widget.AppCompatImageView +import org.signal.core.util.next import org.thoughtcrime.securesms.fonts.TextFont -import org.thoughtcrime.securesms.util.next typealias OnTextFontChanged = (TextFont) -> Unit diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt index 98e22a4d6a..1d1347134c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt @@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.util.ConversationUtil import org.thoughtcrime.securesms.util.TextSecurePreferences import java.util.Optional import androidx.core.app.Person as PersonCompat +import org.signal.core.ui.R as CoreUiR private const val BIG_PICTURE_DIMEN = 500 @@ -237,7 +238,7 @@ sealed class NotificationBuilder(protected val context: Context) { val markAsRead: PendingIntent? = conversation.getMarkAsReadIntent(context) if (markAsRead != null) { val markAsReadAction: NotificationCompat.Action = - NotificationCompat.Action.Builder(R.drawable.symbol_check_24, context.getString(R.string.MessageNotifier_mark_read), markAsRead) + NotificationCompat.Action.Builder(CoreUiR.drawable.symbol_check_24, context.getString(R.string.MessageNotifier_mark_read), markAsRead) .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ) .setShowsUserInterface(false) .build() @@ -251,7 +252,7 @@ sealed class NotificationBuilder(protected val context: Context) { val markAsRead: PendingIntent? = state.getMarkAsReadIntent(context) if (markAsRead != null) { - val markAllAsReadAction = NotificationCompat.Action(R.drawable.symbol_check_24, context.getString(R.string.MessageNotifier_mark_all_as_read), markAsRead) + val markAllAsReadAction = NotificationCompat.Action(CoreUiR.drawable.symbol_check_24, context.getString(R.string.MessageNotifier_mark_all_as_read), markAsRead) builder.addAction(markAllAsReadAction) builder.extend(NotificationCompat.WearableExtender().addAction(markAllAsReadAction)) } @@ -260,7 +261,7 @@ sealed class NotificationBuilder(protected val context: Context) { override fun addTurnOffJoinedNotificationsAction(pendingIntent: PendingIntent?) { if (pendingIntent != null) { val turnOffTheseNotifications = NotificationCompat.Action( - R.drawable.symbol_check_24, + CoreUiR.drawable.symbol_check_24, context.getString(R.string.MessageNotifier_turn_off_these_notifications), pendingIntent ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorHudV2.kt b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorHudV2.kt index 3b8fc26ce6..60b3feaacc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorHudV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorHudV2.kt @@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.scribbles.HSVColorSlider.setUpForColor import org.thoughtcrime.securesms.util.Debouncer import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.visible +import org.signal.core.ui.R as CoreUiR class ImageEditorHudV2 @JvmOverloads constructor( context: Context, @@ -146,9 +147,9 @@ class ImageEditorHudV2 @JvmOverloads constructor( cropAspectLockButton.setOnClickListener { listener?.onCropAspectLock() if (listener?.isCropAspectLocked == true) { - cropAspectLockButton.setImageResource(R.drawable.symbol_crop_lock_24) + cropAspectLockButton.setImageResource(CoreUiR.drawable.symbol_crop_lock_24) } else { - cropAspectLockButton.setImageResource(R.drawable.symbol_crop_unlock_24) + cropAspectLockButton.setImageResource(CoreUiR.drawable.symbol_crop_unlock_24) } } @@ -355,7 +356,7 @@ class ImageEditorHudV2 @JvmOverloads constructor( private fun presentModeDraw() { drawButton.isSelected = true - brushToggle.setImageResource(R.drawable.symbol_brush_pen_24) + brushToggle.setImageResource(CoreUiR.drawable.symbol_brush_pen_24) widthSeekBar.progress = SignalStore.imageEditor.getMarkerPercentage() listener?.onColorChange(getActiveColor()) updateColorIndicator() @@ -368,7 +369,7 @@ class ImageEditorHudV2 @JvmOverloads constructor( private fun presentModeHighlight() { drawButton.isSelected = true - brushToggle.setImageResource(R.drawable.symbol_brush_highlighter_24) + brushToggle.setImageResource(CoreUiR.drawable.symbol_brush_highlighter_24) widthSeekBar.progress = SignalStore.imageEditor.getHighlighterPercentage() listener?.onColorChange(getActiveColor()) updateColorIndicator() diff --git a/app/src/main/java/org/thoughtcrime/securesms/stickers/manage/StickerPackListItems.kt b/app/src/main/java/org/thoughtcrime/securesms/stickers/manage/StickerPackListItems.kt index 777cc576e7..15be90056a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stickers/manage/StickerPackListItems.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stickers/manage/StickerPackListItems.kt @@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.components.transfercontrols.TransferProgressSt import org.thoughtcrime.securesms.stickers.StickerPreviewDataFactory import org.thoughtcrime.securesms.stickers.manage.AvailableStickerPack.DownloadStatus import org.thoughtcrime.securesms.util.DeviceProperties +import org.signal.core.ui.R as CoreUiR @Composable fun StickerPackSectionHeader( @@ -90,7 +91,7 @@ fun AvailableStickerPackRow( ) val readyIcon = ImageVector.vectorResource(R.drawable.symbol_arrow_circle_down_24) - val downloadedIcon = ImageVector.vectorResource(R.drawable.symbol_check_24) + val downloadedIcon = ImageVector.vectorResource(CoreUiR.drawable.symbol_check_24) val startButtonContentDesc = stringResource(R.string.StickerManagement_accessibility_download) val startButtonOnClickLabel = stringResource(R.string.StickerManagement_accessibility_download_pack, pack.record.title) diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenu.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenu.kt index cb63cf5dd0..3b7a3ae88d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenu.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/dialogs/StoryContextMenu.kt @@ -242,7 +242,7 @@ object StoryContextMenu { } ) add( - ActionItem(R.drawable.symbol_save_android_24, context.getString(R.string.save)) { + ActionItem(CoreUiR.drawable.symbol_save_android_24, context.getString(R.string.save)) { callbacks.onSave() } ) diff --git a/core/ui/src/main/java/org/signal/core/ui/compose/SignalIcons.kt b/core/ui/src/main/java/org/signal/core/ui/compose/SignalIcons.kt index 762016d642..a3af36e447 100644 --- a/core/ui/src/main/java/org/signal/core/ui/compose/SignalIcons.kt +++ b/core/ui/src/main/java/org/signal/core/ui/compose/SignalIcons.kt @@ -35,18 +35,27 @@ enum class SignalIcons(private val icon: SignalIcon) : SignalIcon by icon { ArrowEnd(icon(R.drawable.symbol_arrow_end_24)), At(icon(R.drawable.symbol_at_24)), Backup(icon(R.drawable.symbol_backup_24)), + Blur(icon(R.drawable.symbol_blur_24)), + BrushPen(icon(R.drawable.symbol_brush_pen_24)), + BrushHighlighter(icon(R.drawable.symbol_brush_highlighter_24)), Camera(icon(R.drawable.symbol_camera_24)), CameraSwitch(icon(R.drawable.symbol_switch_24)), + Check(icon(R.drawable.symbol_check_24)), CheckCircle(icon(R.drawable.symbol_check_circle_24)), ChevronRight(icon(R.drawable.symbol_chevron_right_24)), Copy(icon(R.drawable.symbol_copy_android_24)), + CropLock(icon(R.drawable.symbol_crop_lock_24)), + CropRotate(icon(R.drawable.symbol_crop_rotate_24)), + CropUnlock(icon(R.drawable.symbol_crop_unlock_24)), + Draw(icon(R.drawable.symbol_draw_24)), Edit(icon(R.drawable.symbol_edit_24)), Emoji(icon(R.drawable.symbol_emoji_24)), ErrorCircle(icon(R.drawable.symbol_error_circle_fill_24)), + File(icon(R.drawable.symbol_file_24)), FlashAuto(icon(R.drawable.symbol_flash_auto_24)), FlashOff(icon(R.drawable.symbol_flash_slash_24)), - File(icon(R.drawable.symbol_file_24)), FlashOn(icon(R.drawable.symbol_flash_24)), + Flip(icon(R.drawable.symbol_flip_24)), Forward(icon(R.drawable.symbol_forward_24)), Info(icon(R.drawable.symbol_info_24)), Keyboard(icon(R.drawable.ic_keyboard_24)), @@ -56,10 +65,15 @@ enum class SignalIcons(private val icon: SignalIcon) : SignalIcon by icon { Phone(icon(R.drawable.symbol_phone_24)), Plus(icon(R.drawable.symbol_plus_24)), QrCode(icon(R.drawable.symbol_qrcode_24)), + QualityHigh(icon(R.drawable.symbol_quality_high_24)), + QualityHighSlash(icon(R.drawable.symbol_quality_high_slash_24)), Recent(icon(R.drawable.symbol_recent_24)), + Save(icon(R.drawable.symbol_save_android_24)), Search(icon(R.drawable.symbol_search_24)), Settings(icon(R.drawable.symbol_settings_android_24)), Share(icon(R.drawable.symbol_share_android_24)), + Sticker(icon(R.drawable.symbol_sticker_24)), + Text(icon(R.drawable.symbol_text_24)), Trash(icon(R.drawable.symbol_trash_24)), X(icon(R.drawable.symbol_x_24)), XCircleFill(icon(R.drawable.symbol_x_circle_fill_24)) diff --git a/app/src/main/res/drawable/symbol_blur_24.xml b/core/ui/src/main/res/drawable/symbol_blur_24.xml similarity index 100% rename from app/src/main/res/drawable/symbol_blur_24.xml rename to core/ui/src/main/res/drawable/symbol_blur_24.xml diff --git a/app/src/main/res/drawable/symbol_brush_highlighter_24.xml b/core/ui/src/main/res/drawable/symbol_brush_highlighter_24.xml similarity index 100% rename from app/src/main/res/drawable/symbol_brush_highlighter_24.xml rename to core/ui/src/main/res/drawable/symbol_brush_highlighter_24.xml diff --git a/app/src/main/res/drawable/symbol_brush_pen_24.xml b/core/ui/src/main/res/drawable/symbol_brush_pen_24.xml similarity index 100% rename from app/src/main/res/drawable/symbol_brush_pen_24.xml rename to core/ui/src/main/res/drawable/symbol_brush_pen_24.xml diff --git a/app/src/main/res/drawable/symbol_check_24.xml b/core/ui/src/main/res/drawable/symbol_check_24.xml similarity index 100% rename from app/src/main/res/drawable/symbol_check_24.xml rename to core/ui/src/main/res/drawable/symbol_check_24.xml diff --git a/app/src/main/res/drawable/symbol_crop_lock_24.xml b/core/ui/src/main/res/drawable/symbol_crop_lock_24.xml similarity index 100% rename from app/src/main/res/drawable/symbol_crop_lock_24.xml rename to core/ui/src/main/res/drawable/symbol_crop_lock_24.xml diff --git a/app/src/main/res/drawable/symbol_crop_rotate_24.xml b/core/ui/src/main/res/drawable/symbol_crop_rotate_24.xml similarity index 100% rename from app/src/main/res/drawable/symbol_crop_rotate_24.xml rename to core/ui/src/main/res/drawable/symbol_crop_rotate_24.xml diff --git a/app/src/main/res/drawable/symbol_crop_unlock_24.xml b/core/ui/src/main/res/drawable/symbol_crop_unlock_24.xml similarity index 100% rename from app/src/main/res/drawable/symbol_crop_unlock_24.xml rename to core/ui/src/main/res/drawable/symbol_crop_unlock_24.xml diff --git a/app/src/main/res/drawable/symbol_draw_24.xml b/core/ui/src/main/res/drawable/symbol_draw_24.xml similarity index 100% rename from app/src/main/res/drawable/symbol_draw_24.xml rename to core/ui/src/main/res/drawable/symbol_draw_24.xml diff --git a/core/ui/src/main/res/drawable/symbol_flip_24.xml b/core/ui/src/main/res/drawable/symbol_flip_24.xml new file mode 100644 index 0000000000..0d02dfb8f0 --- /dev/null +++ b/core/ui/src/main/res/drawable/symbol_flip_24.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/symbol_quality_high_24.xml b/core/ui/src/main/res/drawable/symbol_quality_high_24.xml similarity index 100% rename from app/src/main/res/drawable/symbol_quality_high_24.xml rename to core/ui/src/main/res/drawable/symbol_quality_high_24.xml diff --git a/app/src/main/res/drawable/symbol_quality_high_slash_24.xml b/core/ui/src/main/res/drawable/symbol_quality_high_slash_24.xml similarity index 100% rename from app/src/main/res/drawable/symbol_quality_high_slash_24.xml rename to core/ui/src/main/res/drawable/symbol_quality_high_slash_24.xml diff --git a/app/src/main/res/drawable/symbol_save_android_24.xml b/core/ui/src/main/res/drawable/symbol_save_android_24.xml similarity index 100% rename from app/src/main/res/drawable/symbol_save_android_24.xml rename to core/ui/src/main/res/drawable/symbol_save_android_24.xml diff --git a/core/ui/src/main/res/drawable/symbol_sticker_24.xml b/core/ui/src/main/res/drawable/symbol_sticker_24.xml new file mode 100644 index 0000000000..1f0c29e547 --- /dev/null +++ b/core/ui/src/main/res/drawable/symbol_sticker_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/symbol_text_24.xml b/core/ui/src/main/res/drawable/symbol_text_24.xml similarity index 100% rename from app/src/main/res/drawable/symbol_text_24.xml rename to core/ui/src/main/res/drawable/symbol_text_24.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/EnumUtils.kt b/core/util-jvm/src/main/java/org/signal/core/util/EnumUtils.kt similarity index 72% rename from app/src/main/java/org/thoughtcrime/securesms/util/EnumUtils.kt rename to core/util-jvm/src/main/java/org/signal/core/util/EnumUtils.kt index 628f5f26a3..47b8e6ce84 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/EnumUtils.kt +++ b/core/util-jvm/src/main/java/org/signal/core/util/EnumUtils.kt @@ -1,4 +1,9 @@ -package org.thoughtcrime.securesms.util +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.core.util /** * Treating an Enum as a circular list, returns the "next" diff --git a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendState.kt b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendState.kt index 79142c0e60..63d6de56f4 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/MediaSendState.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/MediaSendState.kt @@ -6,8 +6,11 @@ package org.signal.mediasend import android.net.Uri +import android.os.Parcel import android.os.Parcelable +import kotlinx.parcelize.Parceler import kotlinx.parcelize.Parcelize +import kotlinx.parcelize.WriteWith import org.signal.core.models.media.Media import org.signal.core.models.media.MediaFolder @@ -108,22 +111,38 @@ data class MediaSendState( /** * The [MediaFolder] list available on the system */ - val mediaFolders: List = emptyList(), + val mediaFolders: @WriteWith List = emptyList(), /** * The selected [MediaFolder] for which to display content in the Select screen */ - val selectedMediaFolder: MediaFolder? = null, + val selectedMediaFolder: @WriteWith MediaFolder? = null, /** * The media content for a given selected [MediaFolder] */ - val selectedMediaFolderItems: List = emptyList() + val selectedMediaFolderItems: @WriteWith List = emptyList() ) : Parcelable { /** - * View-once toggle state. + * No-op parcelers for fields that are re-loaded on init and should not + * contribute to the saved-state bundle size. */ + private object TransientMediaFolderListParceler : Parceler> { + override fun create(parcel: Parcel): List = emptyList() + override fun List.write(parcel: Parcel, flags: Int) = Unit + } + + private object TransientMediaFolderParceler : Parceler { + override fun create(parcel: Parcel): MediaFolder? = null + override fun MediaFolder?.write(parcel: Parcel, flags: Int) = Unit + } + + private object TransientMediaListParceler : Parceler> { + override fun create(parcel: Parcel): List = emptyList() + override fun List.write(parcel: Parcel, flags: Int) = Unit + } + enum class ViewOnceToggleState(val code: Int) { OFF(0), ONCE(1); diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/EditorController.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/EditorController.kt new file mode 100644 index 0000000000..53c8b91dd5 --- /dev/null +++ b/feature/media-send/src/main/java/org/signal/mediasend/edit/EditorController.kt @@ -0,0 +1,141 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.mediasend.edit + +import android.net.Uri +import androidx.compose.runtime.Stable +import androidx.compose.runtime.annotation.RememberInComposition +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshots.SnapshotStateMap +import org.signal.imageeditor.core.model.EditorModel + +@Stable +sealed interface EditorController { + + @Stable + class Container @RememberInComposition constructor() { + val controllers = SnapshotStateMap() + + fun getOrCreateImageController(uri: Uri, editorModel: EditorModel): Image { + return controllers.getOrPut(uri) { Image(editorModel) } as Image + } + } + + @Stable + class Image @RememberInComposition constructor(val editorModel: EditorModel) : EditorController { + + override val isUserInEdit: Boolean + get() = mode != Mode.NONE + + var mode: Mode by mutableStateOf(Mode.NONE) + + var isCropLocked: Boolean by mutableStateOf(editorModel.isCropAspectLocked) + private set + + val isUserDrawing: Boolean + get() = mode == Mode.DRAW || mode == Mode.HIGHLIGHT + + val isUserBlurring: Boolean + get() = mode == Mode.BLUR + + val isUserEnteringText: Boolean + get() = mode == Mode.TEXT + + val isUserInsertingSticker: Boolean + get() = mode == Mode.INSERT_STICKER + + fun beginDrawEdit() { + enterDrawMode() + } + + fun beginCropAndRotateEdit() { + enterCropMode() + } + + fun cancelEdit() { + mode = Mode.NONE + } + + fun commitEdit() { + mode = Mode.NONE + } + + fun enterDrawMode() { + mode = Mode.DRAW + } + + fun enterHighlightMode() { + mode = Mode.HIGHLIGHT + } + + fun enterBlurMode() { + mode = Mode.BLUR + } + + fun enterCropMode() { + mode = Mode.CROP + } + + fun enterTextMode() { + mode = Mode.TEXT + } + + fun enterStickerMode() { + mode = Mode.INSERT_STICKER + } + + fun lockCrop() { + editorModel.setCropAspectLock(true) + isCropLocked = true + } + + fun unlockCrop() { + editorModel.setCropAspectLock(false) + isCropLocked = false + } + + fun flip() { + editorModel.flipHorizontal() + } + + fun rotate() { + editorModel.rotate90anticlockwise() + } + + fun toggleImageQuality() { + // TODO + } + + fun saveToDisk() { + // TODO + } + + fun addMedia() { + // TODO + } + + enum class Mode { + NONE, + CROP, + TEXT, + DRAW, + HIGHLIGHT, + BLUR, + MOVE_STICKER, + MOVE_TEXT, + DELETE, + INSERT_STICKER + } + } + + object VideoTrim : EditorController { + override val isUserInEdit: Boolean = false + } + + val isUserInEdit: Boolean +} diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditor.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditor.kt index ddacb62a6c..c2f730e300 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditor.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditor.kt @@ -13,7 +13,7 @@ import org.signal.imageeditor.core.ImageEditorView @Composable fun ImageEditor( - controller: ImageEditorController, + controller: EditorController.Image, modifier: Modifier = Modifier ) { AndroidView( @@ -27,17 +27,17 @@ fun ImageEditor( ) } -private fun mapMode(mode: ImageEditorController.Mode): ImageEditorView.Mode { +private fun mapMode(mode: EditorController.Image.Mode): ImageEditorView.Mode { return when (mode) { - ImageEditorController.Mode.NONE -> ImageEditorView.Mode.MoveAndResize - ImageEditorController.Mode.CROP -> ImageEditorView.Mode.MoveAndResize - ImageEditorController.Mode.TEXT -> ImageEditorView.Mode.MoveAndResize - ImageEditorController.Mode.DRAW -> ImageEditorView.Mode.Draw - ImageEditorController.Mode.HIGHLIGHT -> ImageEditorView.Mode.Draw - ImageEditorController.Mode.BLUR -> ImageEditorView.Mode.Blur - ImageEditorController.Mode.MOVE_STICKER -> ImageEditorView.Mode.MoveAndResize - ImageEditorController.Mode.MOVE_TEXT -> ImageEditorView.Mode.MoveAndResize - ImageEditorController.Mode.DELETE -> ImageEditorView.Mode.MoveAndResize - ImageEditorController.Mode.INSERT_STICKER -> ImageEditorView.Mode.MoveAndResize + EditorController.Image.Mode.NONE -> ImageEditorView.Mode.MoveAndResize + EditorController.Image.Mode.CROP -> ImageEditorView.Mode.MoveAndResize + EditorController.Image.Mode.TEXT -> ImageEditorView.Mode.MoveAndResize + EditorController.Image.Mode.DRAW -> ImageEditorView.Mode.Draw + EditorController.Image.Mode.HIGHLIGHT -> ImageEditorView.Mode.Draw + EditorController.Image.Mode.BLUR -> ImageEditorView.Mode.Blur + EditorController.Image.Mode.MOVE_STICKER -> ImageEditorView.Mode.MoveAndResize + EditorController.Image.Mode.MOVE_TEXT -> ImageEditorView.Mode.MoveAndResize + EditorController.Image.Mode.DELETE -> ImageEditorView.Mode.MoveAndResize + EditorController.Image.Mode.INSERT_STICKER -> ImageEditorView.Mode.MoveAndResize } } diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditorController.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditorController.kt deleted file mode 100644 index c6952a6eb5..0000000000 --- a/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditorController.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2026 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.signal.mediasend.edit - -import androidx.compose.runtime.Stable -import androidx.compose.runtime.annotation.RememberInComposition -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import org.signal.imageeditor.core.model.EditorModel - -@Stable -class ImageEditorController @RememberInComposition constructor( - val editorModel: EditorModel -) { - - var mode: Mode by mutableStateOf(Mode.NONE) - - enum class Mode { - NONE, - CROP, - TEXT, - DRAW, - HIGHLIGHT, - BLUR, - MOVE_STICKER, - MOVE_TEXT, - DELETE, - INSERT_STICKER - } -} diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditorToolbar.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditorToolbar.kt new file mode 100644 index 0000000000..159f6ec851 --- /dev/null +++ b/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditorToolbar.kt @@ -0,0 +1,365 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.mediasend.edit + +import androidx.compose.animation.Crossfade +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp +import org.signal.core.ui.WindowBreakpoint +import org.signal.core.ui.compose.FoldablePortraitDayPreview +import org.signal.core.ui.compose.FoldablePortraitNightPreview +import org.signal.core.ui.compose.IconButtons +import org.signal.core.ui.compose.IconButtons.iconToggleButtonColors +import org.signal.core.ui.compose.PhonePortraitDayPreview +import org.signal.core.ui.compose.PhonePortraitNightPreview +import org.signal.core.ui.compose.Previews +import org.signal.core.ui.compose.SignalIcons +import org.signal.core.ui.compose.copied.androidx.compose.material3.IconButtonColors +import org.signal.core.ui.compose.theme.SignalTheme +import org.signal.core.ui.rememberWindowBreakpoint +import org.signal.core.util.next +import org.signal.imageeditor.core.model.EditorModel +import java.util.EnumMap + +@Composable +fun ImageEditorToolbar( + imageEditorController: EditorController.Image, + modifier: Modifier = Modifier +) { + when (imageEditorController.mode) { + EditorController.Image.Mode.NONE -> { + ImageEditorNoneStateToolbar(imageEditorController, modifier) + } + EditorController.Image.Mode.CROP -> { + ImageEditorCropAndResizeToolbar(imageEditorController, modifier) + } + else -> { + ImageEditorDrawStateToolbar(imageEditorController, modifier) + } + } +} + +/** + * Allows user to perform actions while viewing an editable image. + */ +@Composable +private fun ImageEditorNoneStateToolbar( + imageEditorController: EditorController.Image, + modifier: Modifier = Modifier +) { + OrientedImageEditorToolbar(modifier) { + ImageEditorButton( + imageVector = SignalIcons.BrushPen.imageVector, + onClick = imageEditorController::beginDrawEdit + ) + + ImageEditorButton( + imageVector = SignalIcons.CropRotate.imageVector, + onClick = imageEditorController::beginCropAndRotateEdit + ) + + ImageEditorButton( + imageVector = SignalIcons.QualityHigh.imageVector, + onClick = imageEditorController::toggleImageQuality + ) + + ImageEditorButton( + imageVector = SignalIcons.Save.imageVector, + onClick = imageEditorController::saveToDisk + ) + + ImageEditorButton( + imageVector = SignalIcons.Plus.imageVector, // TODO [alex] - wrong art asset + onClick = imageEditorController::addMedia + ) + } +} + +@Composable +private fun ImageEditorDrawStateToolbar( + imageEditorController: EditorController.Image, + modifier: Modifier = Modifier +) { + OrientedImageEditorToolbar( + modifier = modifier, + leading = { + CommitButton(imageEditorController) + }, + trailing = { + DiscardButton(imageEditorController) + } + ) { + ImageEditorToggleButton( + imageVector = SignalIcons.Draw.imageVector, + checked = imageEditorController.isUserDrawing, + onCheckChanged = { + if (!imageEditorController.isUserDrawing) { + imageEditorController.enterDrawMode() + } + } + ) + + ImageEditorToggleButton( + imageVector = SignalIcons.Text.imageVector, + checked = imageEditorController.isUserEnteringText, + onCheckChanged = { + if (!imageEditorController.isUserEnteringText) { + imageEditorController.enterTextMode() + } + } + ) + + ImageEditorToggleButton( + imageVector = SignalIcons.Sticker.imageVector, + checked = imageEditorController.isUserInsertingSticker, + onCheckChanged = { + if (!imageEditorController.isUserInsertingSticker) { + imageEditorController.enterStickerMode() + } + } + ) + + ImageEditorToggleButton( + imageVector = SignalIcons.Blur.imageVector, + checked = imageEditorController.isUserBlurring, + onCheckChanged = { + if (!imageEditorController.isUserBlurring) { + imageEditorController.enterBlurMode() + } + } + ) + } +} + +@Composable +private fun ImageEditorCropAndResizeToolbar( + imageEditorController: EditorController.Image, + modifier: Modifier = Modifier +) { + OrientedImageEditorToolbar( + modifier = modifier, + leading = { + CommitButton(imageEditorController) + }, + trailing = { + DiscardButton(imageEditorController) + } + ) { + ImageEditorButton( + imageVector = SignalIcons.CropRotate.imageVector, + onClick = imageEditorController::rotate + ) + + ImageEditorButton( + imageVector = SignalIcons.Flip.imageVector, + onClick = imageEditorController::flip + ) + + val cropLockImageVector = SignalIcons.CropLock.imageVector + val cropUnlockImageVector = SignalIcons.CropUnlock.imageVector + + IconCrossfadeToggleButton( + target = if (imageEditorController.isCropLocked) CropLock.LOCKED else CropLock.UNLOCKED, + setTarget = { target -> + when (target) { + CropLock.LOCKED -> imageEditorController.lockCrop() + CropLock.UNLOCKED -> imageEditorController.unlockCrop() + } + }, + targetToImageMap = remember(cropLockImageVector, cropUnlockImageVector) { + EnumMap( + CropLock::class.java + ).apply { + put(CropLock.LOCKED, cropLockImageVector) + put(CropLock.UNLOCKED, cropUnlockImageVector) + } + } + ) + } +} + +@Composable +private fun CommitButton(imageEditorController: EditorController.Image) { + ImageEditorButton( + imageVector = SignalIcons.Check.imageVector, + onClick = imageEditorController::commitEdit, + colors = IconButtons.iconButtonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer + ) + ) +} + +@Composable +private fun DiscardButton(imageEditorController: EditorController.Image) { + ImageEditorButton( + imageVector = SignalIcons.X.imageVector, + onClick = imageEditorController::cancelEdit, + colors = IconButtons.iconButtonColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant + ) + ) +} + +@Composable +private inline fun > IconCrossfadeToggleButton( + target: E, + crossinline setTarget: (E) -> Unit, + targetToImageMap: EnumMap +) { + IconButtons.IconButton( + onClick = { setTarget(target.next()) } + ) { + Crossfade(target) { enumValue -> + Icon( + imageVector = targetToImageMap[enumValue]!!, + contentDescription = null, // TODO + modifier = Modifier.size(24.dp) + ) + } + } +} + +@Composable +private fun ImageEditorButton( + imageVector: ImageVector, + onClick: () -> Unit, + contentDescription: String? = null, + colors: IconButtonColors = IconButtons.iconButtonColors() +) { + IconButtons.IconButton( + onClick = onClick, + colors = colors + ) { + Icon(imageVector = imageVector, contentDescription = contentDescription, modifier = Modifier.size(24.dp)) + } +} + +@Composable +private fun ImageEditorToggleButton( + imageVector: ImageVector, + checked: Boolean, + onCheckChanged: (Boolean) -> Unit, + contentDescription: String? = null +) { + IconButtons.IconToggleButton( + checked = checked, + onCheckedChange = onCheckChanged, + colors = iconToggleButtonColors( + checkedContentColor = MaterialTheme.colorScheme.onSurface, + checkedContainerColor = SignalTheme.colors.colorTransparentInverse2 + ) + ) { + Icon(imageVector = imageVector, contentDescription = contentDescription, modifier = Modifier.size(24.dp)) + } +} + +@Composable +private fun OrientedImageEditorToolbar( + modifier: Modifier = Modifier, + leading: @Composable () -> Unit = {}, + trailing: @Composable () -> Unit = {}, + content: @Composable () -> Unit +) { + val windowBreakpoint = rememberWindowBreakpoint() + val isRow = windowBreakpoint == WindowBreakpoint.SMALL + + if (isRow) { + Row(modifier = modifier.height(48.dp)) { + leading() + + Row( + modifier = Modifier + .fillMaxHeight() + .background(color = MaterialTheme.colorScheme.surfaceVariant, shape = RoundedCornerShape(percent = 50)) + ) { + content() + } + + trailing() + } + } else { + Column(modifier = modifier.width(48.dp)) { + leading() + + Column( + modifier = Modifier + .fillMaxWidth() + .background(color = MaterialTheme.colorScheme.surfaceVariant, shape = RoundedCornerShape(percent = 50)) + ) { + content() + } + + trailing() + } + } +} + +@PhonePortraitDayPreview +@PhonePortraitNightPreview +@FoldablePortraitDayPreview +@FoldablePortraitNightPreview +@Composable +private fun ImageEditorNoneStateToolbarPreview() { + Previews.Preview { + ImageEditorNoneStateToolbar( + imageEditorController = remember { + EditorController.Image(EditorModel.create(0)) + } + ) + } +} + +@PhonePortraitDayPreview +@PhonePortraitNightPreview +@FoldablePortraitDayPreview +@FoldablePortraitNightPreview +@Composable +private fun ImageEditorDrawStateToolbarPreview() { + Previews.Preview { + ImageEditorDrawStateToolbar( + imageEditorController = remember { + EditorController.Image(EditorModel.create(0)).apply { + enterDrawMode() + } + } + ) + } +} + +@PhonePortraitDayPreview +@PhonePortraitNightPreview +@FoldablePortraitDayPreview +@FoldablePortraitNightPreview +@Composable +private fun ImageEditorCropAndResizeToolbarPreview() { + Previews.Preview { + ImageEditorCropAndResizeToolbar( + imageEditorController = remember { + EditorController.Image(EditorModel.create(0)).apply { + enterCropMode() + } + } + ) + } +} + +private enum class CropLock { + LOCKED, UNLOCKED +} diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditorToolbars.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditorToolbars.kt deleted file mode 100644 index a64abbb0fa..0000000000 --- a/feature/media-send/src/main/java/org/signal/mediasend/edit/ImageEditorToolbars.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2026 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.signal.mediasend.edit - -import androidx.compose.runtime.Composable - -/** - * Allows user to perform actions while viewing an editable image. - */ -@Composable -fun ImageEditorTopLevelToolbar( - imageEditorController: ImageEditorController -) { - // Draw -- imageEditorController draw mode - // Crop&Rotate -- imageEditorController crop mode - // Quality -- callback toggle quality - // Save -- callback save to disk - // Add -- callback go to media select -} - -interface ImageEditorToolbarsCallback { - - object Empty : ImageEditorToolbarsCallback -} diff --git a/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt b/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt index d12ed0938a..57416003f3 100644 --- a/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt +++ b/feature/media-send/src/main/java/org/signal/mediasend/edit/MediaEditScreen.kt @@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -28,12 +27,13 @@ import androidx.compose.ui.unit.dp import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.rememberNavBackStack -import androidx.window.core.layout.WindowSizeClass import kotlinx.coroutines.launch import org.signal.core.models.media.Media +import org.signal.core.ui.WindowBreakpoint import org.signal.core.ui.compose.AllDevicePreviews import org.signal.core.ui.compose.Previews -import org.signal.core.util.ContentTypeUtil +import org.signal.core.ui.rememberWindowBreakpoint +import org.signal.imageeditor.core.model.EditorModel import org.signal.mediasend.EditorState import org.signal.mediasend.MediaSendNavKey import org.signal.mediasend.MediaSendState @@ -46,7 +46,6 @@ fun MediaEditScreen( backStack: NavBackStack, videoEditorSlot: @Composable () -> Unit = {} ) { - val isFocusedMediaVideo = ContentTypeUtil.isVideoType(state.focusedMedia?.contentType) val scope = rememberCoroutineScope() val pagerState = rememberPagerState( @@ -59,15 +58,28 @@ fun MediaEditScreen( .fillMaxSize() .navigationBarsPadding() ) { + val isSmallWindowBreakpoint = rememberWindowBreakpoint() == WindowBreakpoint.SMALL + val controllers = remember { EditorController.Container() } + + val currentController = state.focusedMedia?.let { + when (val editorState = state.editorStateMap[it.uri]) { + is EditorState.Image -> controllers.getOrCreateImageController(it.uri, editorState.model) + is EditorState.VideoTrim -> EditorController.VideoTrim + null -> error("Invalid editor state.") + } + } + HorizontalPager( state = pagerState, modifier = Modifier.fillMaxSize(), - snapPosition = SnapPosition.Center + snapPosition = SnapPosition.Center, + userScrollEnabled = currentController?.isUserInEdit != true ) { index -> - when (val editorState = state.editorStateMap[state.selectedMedia[index].uri]) { + val uri = state.selectedMedia[index].uri + when (val editorState = state.editorStateMap[uri]) { is EditorState.Image -> { ImageEditor( - controller = remember { ImageEditorController(editorState.model) }, + controller = controllers.getOrCreateImageController(uri, editorState.model), modifier = Modifier.fillMaxSize() ) } @@ -104,10 +116,13 @@ fun MediaEditScreen( ) } - if (isFocusedMediaVideo) { - // Video editor hud - } else if (!currentWindowAdaptiveInfo().windowSizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND)) { - // Image editor HU + when (currentController) { + is EditorController.Image -> { + if (isSmallWindowBreakpoint) { + ImageEditorToolbar(imageEditorController = currentController) + } + } + is EditorController.VideoTrim, null -> Unit } AddAMessageRow( @@ -120,6 +135,13 @@ fun MediaEditScreen( .padding(bottom = 16.dp) ) } + + if (!isSmallWindowBreakpoint && currentController is EditorController.Image) { + ImageEditorToolbar( + imageEditorController = currentController, + modifier = Modifier.align(Alignment.CenterEnd).padding(end = 24.dp) + ) + } } } @@ -132,7 +154,10 @@ private fun MediaEditScreenPreview() { MediaEditScreen( state = MediaSendState( selectedMedia = selectedMedia, - focusedMedia = selectedMedia.first() + focusedMedia = selectedMedia.first(), + editorStateMap = mutableMapOf( + selectedMedia.first().uri to EditorState.Image(EditorModel.create(0)) + ) ), callback = MediaEditScreenCallback.Empty, backStack = rememberNavBackStack(MediaSendNavKey.Edit),