diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c98bcb5..61df12b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,6 +54,7 @@ jobs: - name: Validate patch tooling run: | + python3 tools/test_activity_theme.py -v python3 tools/test_export_patches.py -v python3 tools/test_conversation_patch.py -v python3 tools/test_signal_830_patch.py -v diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 1200c89..c3d6b1d 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -22,7 +22,7 @@ jobs: strategy: fail-fast: false matrix: - signal: [pinned, v8.31.1] + signal: [pinned, v8.32.1] steps: - name: Check out Vesper patchset @@ -33,6 +33,7 @@ jobs: - name: Validate patch tooling run: | + python3 tools/test_activity_theme.py -v python3 tools/test_export_patches.py -v python3 tools/test_conversation_patch.py -v python3 tools/test_signal_830_patch.py -v diff --git a/patches/0001-feat-add-Material-You-dynamic-theming.patch b/patches/0001-feat-add-Material-You-dynamic-theming.patch index 41cc831..de3a0f0 100644 --- a/patches/0001-feat-add-Material-You-dynamic-theming.patch +++ b/patches/0001-feat-add-Material-You-dynamic-theming.patch @@ -51,7 +51,6 @@ Signed-off-by: amber .../securesms/recipients/Recipient.kt | 3 +- .../recipients/ui/RecipientPicker.kt | 10 +- .../revealable/ViewOnceMessageActivity.java | 15 + - .../ImageEditorStickerSelectActivity.java | 10 + .../stories/viewer/StoryViewerActivity.kt | 9 + .../DynamicConversationSettingsTheme.java | 5 + .../util/DynamicMediaPreviewTheme.java | 18 + @@ -78,7 +77,7 @@ Signed-off-by: amber .../core/ui/compose/theme/SignalTheme.kt | 208 +++++++++-- .../org/signal/core/ui/fonts/SignalSymbols.kt | 9 +- .../org/signal/core/ui/util/ThemeUtil.java | 27 +- - 71 files changed, 989 insertions(+), 324 deletions(-) + 70 files changed, 979 insertions(+), 324 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/DynamicMediaPreviewTheme.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/AvatarPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/AvatarPreviewActivity.java @@ -117,22 +116,22 @@ index 4982c2605f..22721e75df 100644 setContentView(R.layout.contact_photo_preview_activity); diff --git a/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt -index 7bad7aeea0..2f190cfcaf 100644 +index 660d143144..0a766de89a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MainActivity.kt -@@ -54,5 +54,4 @@ import androidx.compose.ui.draw.clip +@@ -53,5 +53,4 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalResources -@@ -89,4 +88,5 @@ import org.signal.core.ui.compose.split.rememberListDetailPaneLayout +@@ -88,4 +87,5 @@ import org.signal.core.ui.compose.split.rememberListDetailPaneLayout import org.signal.core.ui.compose.split.rememberListDetailPaneMetrics import org.signal.core.ui.compose.systemBarsCompat +import org.signal.core.ui.compose.theme.colorAttribute import org.signal.core.ui.compose.theme.SignalTheme import org.signal.core.ui.permissions.Permissions -@@ -418,10 +418,9 @@ class MainActivity : +@@ -416,10 +416,9 @@ class MainActivity : val isConversationFullscreen = !isSplitPane && detailLocation is MainDetailRoute.Conversation - val context = LocalContext.current @@ -144,7 +143,7 @@ index 7bad7aeea0..2f190cfcaf 100644 + else -> colorAttribute(org.signal.core.ui.R.attr.vesper_colorSurface2).toArgb() } -@@ -438,4 +437,8 @@ class MainActivity : +@@ -436,4 +435,8 @@ class MainActivity : enableEdgeToEdge() } + @@ -153,7 +152,7 @@ index 7bad7aeea0..2f190cfcaf 100644 + } } -@@ -621,5 +624,5 @@ class MainActivity : +@@ -611,5 +614,5 @@ class MainActivity : modifier = Modifier .clip(contentLayoutData.navigationBarShape) - .background(color = SignalTheme.colors.colorSurface2) @@ -209,7 +208,7 @@ index 7b8a165745..acc7ed68e6 100644 } ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java -index bebf8b99d8..f8de387889 100644 +index 32fe7bcdd0..e2aa22be38 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ConversationItemFooter.java @@ -26,4 +26,6 @@ import com.airbnb.lottie.LottieProperty; @@ -586,29 +585,29 @@ index 960882133d..9e57182fc2 100644 super.onCreate(savedInstanceState) enableEdgeToEdge() diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/WebRtcCallActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/WebRtcCallActivity.kt -index dfe206f9db..36679fddb7 100644 +index 2a325766f6..50e1ed32ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/WebRtcCallActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/v2/WebRtcCallActivity.kt -@@ -86,4 +86,6 @@ import org.thoughtcrime.securesms.service.webrtc.CallLinkDisconnectReason +@@ -87,4 +87,6 @@ import org.thoughtcrime.securesms.service.webrtc.CallLinkDisconnectReason import org.thoughtcrime.securesms.service.webrtc.SignalCallManager import org.thoughtcrime.securesms.sms.MessageSender +import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme +import org.thoughtcrime.securesms.util.DynamicTheme import org.thoughtcrime.securesms.util.FullscreenHelper import org.thoughtcrime.securesms.util.RemoteConfig -@@ -146,4 +148,5 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re +@@ -153,4 +155,5 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re private val callPermissionsDialogController = CallPermissionsDialogController() private val eventBusSubscriber = EventBusSubscriber() + private val theme: DynamicTheme = DynamicNoActionBarTheme() private val mediaProjectionLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == RESULT_OK && result.data != null) { -@@ -164,4 +167,5 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re +@@ -171,4 +174,5 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re @SuppressLint("MissingInflatedId") override fun onCreate(savedInstanceState: Bundle?) { + theme.onCreate(this) val callIntent: CallIntent = getCallIntent() Log.i(TAG, "onCreate(${callIntent.isStartedFromFullScreen})") -@@ -279,4 +283,5 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re +@@ -286,4 +290,5 @@ class WebRtcCallActivity : BaseActivity(), SafetyNumberChangeDialog.Callback, Re Log.i(TAG, "onResume()") super.onResume() + theme.onResume(this) @@ -626,7 +625,7 @@ index 0ac0d01c12..05fffba34f 100644 callbacks.onDeletePrivateStory(model.story, model.isSelected) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java -index 882a591f30..a94e529677 100644 +index 2965dd3349..dca24dd084 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -130,4 +130,5 @@ import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory; @@ -635,14 +634,14 @@ index 882a591f30..a94e529677 100644 +import org.thoughtcrime.securesms.messages.PreservedRemoteDelete; import org.thoughtcrime.securesms.mms.ImageSlide; import org.thoughtcrime.securesms.mms.PartAuthority; -@@ -1830,5 +1831,5 @@ public final class ConversationItem extends RelativeLayout implements BindableCo +@@ -1861,5 +1862,5 @@ public final class ConversationItem extends RelativeLayout implements BindableCo URLSpan span = new InterceptableLongClickCopyLinkSpan(placeholder.getValue(), urlClickListener, - ContextCompat.getColor(getContext(), isReleaseNotes ? R.color.release_notes_bubble_text : R.color.signal_accent_primary), + ThemeUtil.getThemedColor(getContext(), isReleaseNotes ? R.color.release_notes_bubble_text : R.attr.signal_accent_primary), isReleaseNotes); -@@ -2003,9 +2004,13 @@ public final class ConversationItem extends RelativeLayout implements BindableCo +@@ -2034,9 +2035,13 @@ public final class ConversationItem extends RelativeLayout implements BindableCo reactionsView.setReactions(current.getReactions()); reactionsView.setBubbleWidth(width); - reactionsView.setOnClickListener(v -> { @@ -661,10 +660,10 @@ index 882a591f30..a94e529677 100644 } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java -index af7845c763..d6d9e1cd9a 100644 +index da4c50779a..8fe761560f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateItem.java -@@ -856,9 +856,9 @@ public final class ConversationUpdateItem extends FrameLayout +@@ -827,9 +827,9 @@ public final class ConversationUpdateItem extends FrameLayout if (collapsibleType != null) { SpannableStringBuilder text = new SpannableStringBuilder() - .append(SignalSymbols.getSpannedString(getContext(), SignalSymbols.Weight.BOLD, getCollapsibleSymbol(collapsibleType), org.signal.core.ui.R.color.signal_colorOnSurfaceVariant)) @@ -676,7 +675,7 @@ index af7845c763..d6d9e1cd9a 100644 + .append(SignalSymbols.getSpannedString(getContext(), SignalSymbols.Weight.BOLD, collapsedState == CollapsedState.HEAD_EXPANDED ? SignalSymbols.Glyph.CHEVRON_UP : SignalSymbols.Glyph.CHEVRON_DOWN, com.google.android.material.R.attr.colorOnSurfaceVariant)); collapsedButton.setText(text); collapsedButton.setOnClickListener(v -> { -@@ -880,5 +880,5 @@ public final class ConversationUpdateItem extends FrameLayout +@@ -851,5 +851,5 @@ public final class ConversationUpdateItem extends FrameLayout } else { collapsedButton.setBackgroundResource(R.drawable.rounded_rectangle_38); - collapsedButton.setBackgroundTintList(AppCompatResources.getColorStateList(getContext(), org.signal.core.ui.R.color.signal_colorSurface1)); @@ -734,7 +733,7 @@ index 5b58874ee0..10a6aab945 100644 PorterDuff.Mode.SRC_IN ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt -index 04b4e2e0d1..acd82ef283 100644 +index a81af4942b..a632c979f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -6,4 +6,6 @@ @@ -744,7 +743,7 @@ index 04b4e2e0d1..acd82ef283 100644 + import android.Manifest import android.animation.ValueAnimator -@@ -2193,7 +2195,7 @@ class ConversationFragment : +@@ -2249,7 +2251,7 @@ class ConversationFragment : binding.toolbar.setNavigationIcon(CoreUiR.drawable.symbol_arrow_start_24) binding.toolbar.navigationIcon?.setTint( - ContextCompat.getColor( @@ -754,7 +753,7 @@ index 04b4e2e0d1..acd82ef283 100644 + if (viewModel.wallpaperSnapshot != null) CoreUiR.color.signal_colorNeutralInverse else MaterialR.attr.colorOnSurfaceVariant ) ) -@@ -2279,10 +2281,10 @@ class ConversationFragment : +@@ -2337,10 +2339,10 @@ class ConversationFragment : val wallpaperEnabled = chatWallpaper != null || recipient.isReleaseNotes - val toolbarTint = ContextCompat.getColor( @@ -767,7 +766,7 @@ index 04b4e2e0d1..acd82ef283 100644 + MaterialR.attr.colorOnSurface } ) -@@ -2310,7 +2312,7 @@ class ConversationFragment : +@@ -2368,7 +2370,7 @@ class ConversationFragment : binding.scrollDateHeader.setTextColor( - ContextCompat.getColor( @@ -777,7 +776,7 @@ index 04b4e2e0d1..acd82ef283 100644 + if (wallpaperEnabled) R.color.sticky_header_foreground_wallpaper else MaterialR.attr.colorOnSurfaceVariant ) ) -@@ -2352,8 +2354,8 @@ class ConversationFragment : +@@ -2410,8 +2412,8 @@ class ConversationFragment : R.color.conversation_navigation_wallpaper } else { - CoreUiR.color.signal_colorBackground @@ -788,7 +787,7 @@ index 04b4e2e0d1..acd82ef283 100644 + chatScrims.navigationBarColor = ThemeUtil.getThemedColor(requireContext(), navColor) chatScrims.attachmentKeyboardColor = androidx.compose.ui.graphics.Color( ContextCompat.getColor( -@@ -4795,10 +4797,10 @@ class ConversationFragment : +@@ -4862,10 +4864,10 @@ class ConversationFragment : } - val toolbarTextAndIconColor = ContextCompat.getColor( @@ -997,7 +996,7 @@ index 0b2327b9c3..e4e604106b 100644 ImageViewCompat.setImageTintList(navButton, iconTint) ImageViewCompat.setImageTintList(clearButton, iconTint) diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.kt -index c9e44baaa4..843ce4c68e 100644 +index 26693931ac..0a16b932f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/SettingsValues.kt @@ -39,4 +39,6 @@ class SettingsValues internal constructor(store: KeyValueStore, private val cont @@ -1007,14 +1006,14 @@ index c9e44baaa4..843ce4c68e 100644 + private const val IGNORE_REMOTE_DELETE = "settings.ignore.remote.delete" const val THEME = "settings.theme" const val MESSAGE_FONT_SIZE = "settings.message.font.size" -@@ -136,4 +138,6 @@ class SettingsValues internal constructor(store: KeyValueStore, private val cont +@@ -138,4 +140,6 @@ class SettingsValues internal constructor(store: KeyValueStore, private val cont THREAD_TRIM_ENABLED, THEME, + DYNAMIC_COLORS_ENABLED, + IGNORE_REMOTE_DELETE, MESSAGE_FONT_SIZE, PREFER_SYSTEM_EMOJI, -@@ -223,9 +227,24 @@ class SettingsValues internal constructor(store: KeyValueStore, private val cont +@@ -228,9 +232,24 @@ class SettingsValues internal constructor(store: KeyValueStore, private val cont } + fun setTheme(theme: Theme, useDynamicColors: Boolean) { @@ -1325,7 +1324,7 @@ index 76cc7dc4d6..9d29648622 100644 binding.usernameTextFocusedStroke.setBackgroundColor(color); diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.kt -index 59bf8c3a04..4822091569 100644 +index b9f8f4b267..61185fefab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.kt @@ -62,5 +62,4 @@ import java.util.LinkedList @@ -1334,7 +1333,7 @@ index 59bf8c3a04..4822091569 100644 -import org.signal.core.ui.R as CoreUiR /** -@@ -735,5 +734,5 @@ class Recipient( +@@ -739,5 +738,5 @@ class Recipient( if (isIndividual && !isSelf) { val isLtr = ViewUtil.isLtr(context) - val chevronGlyph = SignalSymbols.getSpannedString(context, SignalSymbols.Weight.BOLD, if (isLtr) SignalSymbols.Glyph.CHEVRON_RIGHT else SignalSymbols.Glyph.CHEVRON_LEFT, CoreUiR.color.signal_colorOutline) @@ -1409,35 +1408,6 @@ index c4d56a9faa..38a41459e9 100644 + } private final Handler handler = new Handler(Looper.getMainLooper()); -diff --git a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorStickerSelectActivity.java b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorStickerSelectActivity.java -index 28f099e86e..93e1b5f172 100644 ---- a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorStickerSelectActivity.java -+++ b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorStickerSelectActivity.java -@@ -27,4 +27,6 @@ import org.thoughtcrime.securesms.scribbles.stickers.ScribbleStickersFragment; - import org.thoughtcrime.securesms.stickers.StickerEventListener; - import org.thoughtcrime.securesms.stickers.manage.StickerManagementScreen; -+import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; -+import org.thoughtcrime.securesms.util.DynamicTheme; - import org.thoughtcrime.securesms.util.ViewUtil; - -@@ -32,4 +34,11 @@ public final class ImageEditorStickerSelectActivity extends AppCompatActivity im - - public static final String EXTRA_FEATURE_STICKER = "imageEditor.featureSticker"; -+ private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); -+ -+ @Override -+ protected void onResume() { -+ super.onResume(); -+ dynamicTheme.onResume(this); -+ } - - @Override -@@ -41,4 +50,5 @@ public final class ImageEditorStickerSelectActivity extends AppCompatActivity im - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { -+ dynamicTheme.onCreate(this); - EdgeToEdge.enable(this); - super.onCreate(savedInstanceState); diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt index ed76d94e02..2afe86ab00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/StoryViewerActivity.kt @@ -1902,7 +1872,7 @@ index da485359b5..e8bd1b2026 100644 ?attr/signal_background_primary @drawable/ic_splash diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml -index 5991f98857..c9ee61c535 100644 +index 3565ca6821..41d98ade43 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -51,5 +51,5 @@ diff --git a/patches/0005-feat-finish-Vesper-settings-and-help-surfaces.patch b/patches/0005-feat-finish-Vesper-settings-and-help-surfaces.patch index 41aea82..2fdfacf 100644 --- a/patches/0005-feat-finish-Vesper-settings-and-help-surfaces.patch +++ b/patches/0005-feat-finish-Vesper-settings-and-help-surfaces.patch @@ -9,7 +9,6 @@ Signed-off-by: amber --- .../securesms/ApplicationContext.java | 6 + .../securesms/apkupdate/ApkUpdateInstaller.kt | 14 ++- - .../settings/DSLSettingsActivity.kt | 13 ++- .../settings/app/AppSettingsFragment.kt | 26 ++--- .../settings/app/AppSettingsState.kt | 4 +- .../settings/app/AppSettingsViewModel.kt | 4 +- @@ -24,24 +23,21 @@ Signed-off-by: amber .../main/res/layout/dsl_settings_toolbar.xml | 2 +- app/src/main/res/values/strings.xml | 5 + .../securesms/jobs/ApkUpdateJobTest.kt | 34 ++++++ - 17 files changed, 303 insertions(+), 50 deletions(-) + 16 files changed, 292 insertions(+), 48 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/settings/app/help/HelpSettingsViewModel.kt create mode 100644 app/src/test/java/org/thoughtcrime/securesms/jobs/ApkUpdateJobTest.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java -index e8f58475f8..d83547e67c 100644 +index ed402a7d88..29736677de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java -@@ -70,6 +70,7 @@ import org.thoughtcrime.securesms.gcm.FcmFetchManager; - import org.thoughtcrime.securesms.glide.SignalGlideComponents; +@@ -74,4 +74,5 @@ import org.thoughtcrime.securesms.glide.SignalGlideComponents; import org.thoughtcrime.securesms.jobmanager.impl.SealedSenderConstraint; import org.thoughtcrime.securesms.jobs.AccountConsistencyWorkerJob; +import org.thoughtcrime.securesms.jobs.ApkUpdateJob; import org.thoughtcrime.securesms.jobs.BackupRefreshJob; import org.thoughtcrime.securesms.jobs.BackupSubscriptionCheckJob; - import org.thoughtcrime.securesms.jobs.BuildExpirationConfirmationJob; -@@ -287,6 +288,10 @@ public class ApplicationContext extends Application implements AppForegroundObse - BackupRefreshJob.enqueueIfNecessary(); +@@ -293,4 +294,8 @@ public class ApplicationContext extends Application implements AppForegroundObse InAppPaymentAuthCheckJob.enqueueIfNeeded(); RemoteConfig.refreshIfNecessary(); + if (BuildConfig.MANAGES_APP_UPDATES && RemoteConfig.internalUser()) { @@ -50,36 +46,29 @@ index e8f58475f8..d83547e67c 100644 + } RetrieveProfileJob.enqueueRoutineFetchIfNecessary(); executePendingContactSync(); - KeyCachingService.onAppForegrounded(this); -@@ -371,6 +376,7 @@ public class ApplicationContext extends Application implements AppForegroundObse - @VisibleForTesting +@@ -381,4 +386,5 @@ public class ApplicationContext extends Application implements AppForegroundObse protected void initializeLogging() { Log.initialize(RemoteConfig::internalUser, AndroidLogger.INSTANCE, PersistentLogger.getInstance(this)); + Log.setLogging(org.thoughtcrime.securesms.util.VesperLogPreferences.isLogEnabled(this)); SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger()); - SignalProtocolLoggerProvider.initializeLogging(BuildConfig.LIBSIGNAL_LOG_LEVEL); diff --git a/app/src/main/java/org/thoughtcrime/securesms/apkupdate/ApkUpdateInstaller.kt b/app/src/main/java/org/thoughtcrime/securesms/apkupdate/ApkUpdateInstaller.kt index af21018d7d..090d3d5ce2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/apkupdate/ApkUpdateInstaller.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/apkupdate/ApkUpdateInstaller.kt -@@ -11,12 +11,14 @@ import android.content.Context - import android.content.Intent +@@ -12,4 +12,5 @@ import android.content.Intent import android.content.pm.PackageInstaller import android.os.Build +import org.greenrobot.eventbus.EventBus import org.signal.core.util.AppForegroundObserver import org.signal.core.util.PendingIntentFlags - import org.signal.core.util.StreamUtil - import org.signal.core.util.getDownloadManager +@@ -18,4 +19,5 @@ import org.signal.core.util.getDownloadManager import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.dependencies.AppDependencies +import org.thoughtcrime.securesms.events.WebRtcViewModel import org.thoughtcrime.securesms.jobs.ApkUpdateJob import org.thoughtcrime.securesms.keyvalue.SignalStore - import org.thoughtcrime.securesms.util.FileUtils -@@ -186,12 +188,12 @@ object ApkUpdateInstaller { - } +@@ -187,11 +189,11 @@ object ApkUpdateInstaller { private fun shouldAutoUpdate(): Boolean { - // Once we have designs for allowing users to opt-out of auto-updates, we can re-enable this @@ -97,45 +86,11 @@ index af21018d7d..090d3d5ce2 100644 + !isCallActive } } -diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsActivity.kt -index b37ef04415..4c80f78b88 100644 ---- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsActivity.kt -+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/DSLSettingsActivity.kt -@@ -26,8 +26,19 @@ open class DSLSettingsActivity : PassphraseRequiredActivity() { - protected lateinit var navController: NavController - private set - -+ private var themeAppliedBeforeInflation = false -+ -+ override fun onPreCreate() { -+ super.onPreCreate() -+ dynamicTheme.onCreate(this) -+ themeAppliedBeforeInflation = true -+ } -+ - override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { - enableSettingsEdgeToEdge() -+ check(themeAppliedBeforeInflation) { -+ "The dynamic settings theme must be applied before inflating views." -+ } - - setContentView(R.layout.dsl_settings_activity) - -@@ -49,8 +60,6 @@ open class DSLSettingsActivity : PassphraseRequiredActivity() { - navController = fragment.navController - } - -- dynamicTheme.onCreate(this) -- - onBackPressedDispatcher.addCallback(this, OnBackPressed()) - } - diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt -index 599b8d0dc1..9180109df0 100644 +index 5eeb1de0f1..1669a37661 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsFragment.kt -@@ -520,21 +520,21 @@ private fun AppSettingsContent( - ) +@@ -521,19 +521,19 @@ private fun AppSettingsContent( } - if (state.showInternalPreferences) { @@ -168,13 +123,11 @@ index 599b8d0dc1..9180109df0 100644 + if (state.showInternalPreferences) { item { Rows.TextRow( - text = stringResource(R.string.preferences__internal_preferences), diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsState.kt index 630e4f79aa..a4e7a879b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsState.kt -@@ -1,8 +1,8 @@ - package org.thoughtcrime.securesms.components.settings.app +@@ -2,6 +2,6 @@ package org.thoughtcrime.securesms.components.settings.app import androidx.compose.runtime.Immutable +import org.thoughtcrime.securesms.BuildConfig @@ -182,36 +135,36 @@ index 630e4f79aa..a4e7a879b5 100644 -import org.thoughtcrime.securesms.util.Environment import org.thoughtcrime.securesms.util.RemoteConfig - @Immutable -@@ -15,7 +15,7 @@ data class AppSettingsState( - val clientDeprecated: Boolean, +@@ -16,5 +16,5 @@ data class AppSettingsState( val showInternalPreferences: Boolean = RemoteConfig.internalUser, val showPayments: Boolean = SignalStore.payments.paymentsAvailability.showPaymentsMenu(), - val showAppUpdates: Boolean = Environment.IS_NIGHTLY, + val showAppUpdates: Boolean = BuildConfig.MANAGES_APP_UPDATES, val backupFailureState: BackupFailureState = BackupFailureState.NONE ) { - fun isRegisteredAndUpToDate(): Boolean { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt index be397caa6a..e62890b03d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/AppSettingsViewModel.kt -@@ -13,2 +13,3 @@ import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord +@@ -12,4 +12,5 @@ import org.thoughtcrime.securesms.conversationlist.model.UnreadPaymentsLiveData + import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.util.RemoteConfig import org.thoughtcrime.securesms.recipients.Recipient -@@ -65,3 +66,4 @@ class AppSettingsViewModel : ViewModel() { + import org.thoughtcrime.securesms.util.livedata.Store +@@ -64,5 +65,6 @@ class AppSettingsViewModel : ViewModel() { + it.copy( hasExpiredGiftBadge = SignalStore.inAppPayments.getExpiredGiftBadge() != null, - backupFailureState = getBackupFailureState() + backupFailureState = getBackupFailureState(), + showInternalPreferences = RemoteConfig.internalUser ) + } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/help/HelpSettingsFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/help/HelpSettingsFragment.kt -index 2c0c55bbb9..68247d948c 100644 +index 0119e8a8c2..fb3e166606 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/help/HelpSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/help/HelpSettingsFragment.kt -@@ -10,16 +10,36 @@ import androidx.compose.foundation.layout.Row - import androidx.compose.foundation.layout.fillMaxWidth +@@ -11,6 +11,14 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.InlineTextContent @@ -226,8 +179,7 @@ index 2c0c55bbb9..68247d948c 100644 +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier - import androidx.compose.ui.graphics.vector.ImageVector - import androidx.compose.ui.platform.LocalContext +@@ -19,6 +27,18 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.LinkAnnotation @@ -246,9 +198,7 @@ index 2c0c55bbb9..68247d948c 100644 +import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.signal.core.ui.compose.ComposeFragment import org.signal.core.ui.compose.Dividers - import org.signal.core.ui.compose.Rows -@@ -35,11 +55,15 @@ import org.thoughtcrime.securesms.util.navigation.safeNavigate - +@@ -37,9 +57,13 @@ import org.signal.core.ui.R as CoreUiR class HelpSettingsFragment : ComposeFragment() { + private val viewModel: HelpSettingsViewModel by viewModels() @@ -262,9 +212,7 @@ index 2c0c55bbb9..68247d948c 100644 + var versionTapCount by rememberSaveable { mutableIntStateOf(0) } Scaffolds.Settings( - title = stringResource(R.string.preferences__help), -@@ -77,6 +101,18 @@ class HelpSettingsFragment : ComposeFragment() { - Rows.TextRow( +@@ -79,4 +103,16 @@ class HelpSettingsFragment : ComposeFragment() { text = stringResource(R.string.HelpSettingsFragment__version), label = BuildConfig.VERSION_NAME, + onClick = { @@ -281,9 +229,7 @@ index 2c0c55bbb9..68247d948c 100644 + }, onLongClick = { Util.copyToClipboard(context, BuildConfig.VERSION_NAME) - Toast.makeText(context, R.string.HelpSettingsFragment__copied_to_clipboard, Toast.LENGTH_SHORT).show() -@@ -84,8 +120,30 @@ class HelpSettingsFragment : ComposeFragment() { - ) +@@ -86,6 +122,28 @@ class HelpSettingsFragment : ComposeFragment() { } + item { @@ -312,9 +258,7 @@ index 2c0c55bbb9..68247d948c 100644 + enabled = state.logEnabled, text = stringResource(id = R.string.HelpSettingsFragment__debug_log), onClick = { - navController.safeNavigate(R.id.action_helpSettingsFragment_to_submitDebugLogActivity) -@@ -113,6 +171,42 @@ class HelpSettingsFragment : ComposeFragment() { - } +@@ -115,4 +173,40 @@ class HelpSettingsFragment : ComposeFragment() { item { + val externalLinkId = "vesper-website-icon" @@ -355,9 +299,7 @@ index 2c0c55bbb9..68247d948c 100644 + Row( modifier = Modifier - .fillMaxWidth() -@@ -120,17 +214,16 @@ class HelpSettingsFragment : ComposeFragment() { - verticalAlignment = CenterVertically +@@ -122,11 +216,6 @@ class HelpSettingsFragment : ComposeFragment() { ) { TextAndLabel( - label = StringBuilder().apply { @@ -371,8 +313,7 @@ index 2c0c55bbb9..68247d948c 100644 + inlineContent = inlineContent ) } - } - } +@@ -135,3 +224,7 @@ class HelpSettingsFragment : ComposeFragment() { } } + @@ -443,21 +384,18 @@ diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/ap index 90939af9cb..e2a968c576 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/labs/LabsSettingsFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/labs/LabsSettingsFragment.kt -@@ -81,7 +81,7 @@ private fun LabsSettingsContent( - modifier = Modifier.size(20.dp) +@@ -82,5 +82,5 @@ private fun LabsSettingsContent( ) Text( - text = "These are internal-only pre-release features. They are all in various unfinished states. They may need more polish, finalized designs, or cross-client compatibility before they can be released.", + text = "These are experimental pre-release features. They are all in various unfinished states. They may need more polish, finalized designs, or cross-client compatibility before they can be released.", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(start = 12.dp) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ApkUpdateJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/ApkUpdateJob.kt index 4153c40fbc..93da6ff198 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ApkUpdateJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ApkUpdateJob.kt -@@ -17,11 +17,11 @@ import org.signal.core.util.requireInt - import org.signal.core.util.requireLong +@@ -18,9 +18,9 @@ import org.signal.core.util.requireLong import org.signal.core.util.requireString import org.thoughtcrime.securesms.BuildConfig +import org.thoughtcrime.securesms.R @@ -468,9 +406,7 @@ index 4153c40fbc..93da6ff198 100644 -import org.thoughtcrime.securesms.util.Environment import org.thoughtcrime.securesms.util.FileUtils import java.io.FileInputStream - import java.io.IOException -@@ -82,8 +82,8 @@ class ApkUpdateJob private constructor(parameters: Parameters) : BaseJob(paramet - Log.d(TAG, "Got descriptor: $updateDescriptor") +@@ -83,6 +83,6 @@ class ApkUpdateJob private constructor(parameters: Parameters) : BaseJob(paramet } - if (shouldUpdate(getCurrentAppVersionCode(), updateDescriptor, SignalStore.apkUpdate.lastApkUploadTime, Environment.IS_WEBSITE)) { @@ -479,18 +415,14 @@ index 4153c40fbc..93da6ff198 100644 + Log.i(TAG, "Newer build available. Current: (versionCode: ${getCurrentAppVersionCode()}, buildTime: ${BuildConfig.BUILD_TIMESTAMP}), Update: (versionCode: ${updateDescriptor.versionCode}, buildTime: ${updateDescriptor.buildTimestamp}, uploadTime: ${updateDescriptor.uploadTimestamp})") val digest: ByteArray = Hex.fromStringCondensed(updateDescriptor.digest) val downloadStatus: DownloadStatus = getDownloadStatus(updateDescriptor.url, digest) - -@@ -97,7 +97,7 @@ class ApkUpdateJob private constructor(parameters: Parameters) : BaseJob(paramet - handleDownloadStart(updateDescriptor.url, updateDescriptor.versionName, digest, updateDescriptor.uploadTimestamp ?: 0) +@@ -98,5 +98,5 @@ class ApkUpdateJob private constructor(parameters: Parameters) : BaseJob(paramet } } else { - Log.d(TAG, "Version code is the same or older than our own. Current: (versionCode: ${getCurrentAppVersionCode()}, uploadTime: ${SignalStore.apkUpdate.lastApkUploadTime}), Update: (versionCode: ${updateDescriptor.versionCode}, uploadTime: ${updateDescriptor.uploadTimestamp})") + Log.d(TAG, "Installed build is current. Current: (versionCode: ${getCurrentAppVersionCode()}, buildTime: ${BuildConfig.BUILD_TIMESTAMP}), Update: (versionCode: ${updateDescriptor.versionCode}, buildTime: ${updateDescriptor.buildTimestamp}, uploadTime: ${updateDescriptor.uploadTimestamp})") } - SignalStore.apkUpdate.lastSuccessfulCheck = System.currentTimeMillis() -@@ -161,8 +161,8 @@ class ApkUpdateJob private constructor(parameters: Parameters) : BaseJob(paramet - +@@ -162,6 +162,6 @@ class ApkUpdateJob private constructor(parameters: Parameters) : BaseJob(paramet val downloadRequest = DownloadManager.Request(Uri.parse(uri)).apply { setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI) - setTitle("Downloading Signal update") @@ -499,9 +431,7 @@ index 4153c40fbc..93da6ff198 100644 + setDescription("Downloading ${context.getString(R.string.app_name)} $versionName") setDestinationInExternalFilesDir(context, null, "signal-update.apk") setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN) - } -@@ -206,14 +206,6 @@ class ApkUpdateJob private constructor(parameters: Parameters) : BaseJob(paramet - } +@@ -207,12 +207,4 @@ class ApkUpdateJob private constructor(parameters: Parameters) : BaseJob(paramet } - private fun shouldUpdate(currentVersionCode: Int, updateDescriptor: UpdateDescriptor, lastApkUploadTime: Long, isWebsite: Boolean): Boolean { @@ -514,9 +444,7 @@ index 4153c40fbc..93da6ff198 100644 - private data class UpdateDescriptor( @JsonProperty - val versionCode: Int = 0, -@@ -228,7 +220,10 @@ class ApkUpdateJob private constructor(parameters: Parameters) : BaseJob(paramet - val digest: String? = null, +@@ -229,5 +221,8 @@ class ApkUpdateJob private constructor(parameters: Parameters) : BaseJob(paramet @JsonProperty - val uploadTimestamp: Long? = null @@ -526,9 +454,7 @@ index 4153c40fbc..93da6ff198 100644 + val buildTimestamp: Long? = null ) - private class DownloadStatus(val status: Status, val downloadId: Long) { -@@ -245,3 +240,17 @@ class ApkUpdateJob private constructor(parameters: Parameters) : BaseJob(paramet - } +@@ -246,2 +241,16 @@ class ApkUpdateJob private constructor(parameters: Parameters) : BaseJob(paramet } } + @@ -549,12 +475,15 @@ diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/Miscellaneous index 4c91beb219..9e5c24c148 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.kt -@@ -63,2 +63,4 @@ class MiscellaneousValues internal constructor(store: KeyValueStore, context: Co +@@ -62,4 +62,6 @@ class MiscellaneousValues internal constructor(store: KeyValueStore, context: Co + private const val LAST_APPLIED_PNI_CHANGE_SERVER_TIMESTAMP = "misc.last_applied_pni_change_server_timestamp" private const val LAST_MISSING_PLAY_SERVICES_FCM_VERIFICATION_TIME = "misc.last_missing_play_services_fcm_verification_time" + private const val VESPER_INTERNAL_USER_OVERRIDE_SET = "misc.vesper_internal_user_override_set" + private const val VESPER_INTERNAL_USER_OVERRIDE = "misc.vesper_internal_user_override" private const val LAST_PROCESSED_SHARE_DATA_TIMESTAMP = "misc.last_processed_share_data_timestamp" -@@ -451,2 +453,26 @@ class MiscellaneousValues internal constructor(store: KeyValueStore, context: Co + private const val DIRECTORY_REFRESH_TIME = "misc.directory_refresh_time" +@@ -450,4 +452,28 @@ class MiscellaneousValues internal constructor(store: KeyValueStore, context: Co + var lastMissingPlayServicesFcmVerificationTime: Long by longValue(LAST_MISSING_PLAY_SERVICES_FCM_VERIFICATION_TIME, 0) + var vesperInternalUserOverride: Boolean? + get() { @@ -581,12 +510,12 @@ index 4c91beb219..9e5c24c148 100644 + } + /** + * High-water mark of the most recently consumed share payload, as set by diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt -index ab1def357e..2b1a3e85b1 100644 +index 6f34c8ded5..3037482486 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt -@@ -97,9 +97,14 @@ object RemoteConfig { - @Volatile +@@ -98,7 +98,12 @@ object RemoteConfig { var internalUserDisabled: Boolean = false + @Volatile @@ -599,9 +528,7 @@ index ab1def357e..2b1a3e85b1 100644 + val current = parseStoredConfig(SignalStore.remoteConfig.currentConfig) val pending = parseStoredConfig(SignalStore.remoteConfig.pendingConfig) - val changes = computeChanges(current, pending) -@@ -115,6 +120,13 @@ object RemoteConfig { - } +@@ -116,4 +121,11 @@ object RemoteConfig { } + @JvmStatic @@ -613,9 +540,7 @@ index ab1def357e..2b1a3e85b1 100644 + @JvmStatic fun refreshIfNecessary() { - val timeSinceLastFetch = System.currentTimeMillis() - SignalStore.remoteConfig.lastFetchTime -@@ -740,8 +752,10 @@ object RemoteConfig { - key = INTERNAL_USER_KEY, +@@ -741,6 +753,8 @@ object RemoteConfig { hotSwappable = true ) { value -> + val override = internalUserOverride @@ -624,13 +549,11 @@ index ab1def357e..2b1a3e85b1 100644 + override != null -> override underTest -> value.asBoolean(false) Environment.isInternal() -> true - else -> value.asBoolean(false) diff --git a/app/src/main/res/drawable/reaction_pill_background.xml b/app/src/main/res/drawable/reaction_pill_background.xml index a547a8078b..6c1c88f098 100644 --- a/app/src/main/res/drawable/reaction_pill_background.xml +++ b/app/src/main/res/drawable/reaction_pill_background.xml -@@ -1,6 +1,6 @@ - +@@ -2,5 +2,5 @@ - @@ -641,8 +564,7 @@ diff --git a/app/src/main/res/drawable/reaction_pill_background_selected.xml b/a index ead87f4275..37279c6f9a 100644 --- a/app/src/main/res/drawable/reaction_pill_background_selected.xml +++ b/app/src/main/res/drawable/reaction_pill_background_selected.xml -@@ -1,6 +1,6 @@ - +@@ -2,5 +2,5 @@ - @@ -653,28 +575,24 @@ diff --git a/app/src/main/res/layout/dsl_settings_toolbar.xml b/app/src/main/res index 8cf26a497e..4435845403 100644 --- a/app/src/main/res/layout/dsl_settings_toolbar.xml +++ b/app/src/main/res/layout/dsl_settings_toolbar.xml -@@ -19,4 +19,4 @@ - app:titleTextAppearance="@style/Signal.Text.TitleLarge" +@@ -20,3 +20,3 @@ tools:title="Settings" /> - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml -index 1651bc1a01..e32edf7942 100644 +index dad62a5b84..c9b4daec54 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml -@@ -4090,6 +4090,8 @@ - +@@ -4124,4 +4124,6 @@ Internal Preferences + Internal settings enabled + Internal settings disabled Internal Details Stories dialog launcher - -@@ -5589,6 +5591,9 @@ - Licensed under the GNU AGPLv3 +@@ -5573,4 +5575,7 @@ Signal is a 501c3 nonprofit + Vesper Messenger by @@ -682,7 +600,6 @@ index 1651bc1a01..e32edf7942 100644 + https://amber.systems/ - Media quality diff --git a/app/src/test/java/org/thoughtcrime/securesms/jobs/ApkUpdateJobTest.kt b/app/src/test/java/org/thoughtcrime/securesms/jobs/ApkUpdateJobTest.kt new file mode 100644 index 0000000000..24bd3c580e @@ -723,3 +640,6 @@ index 0000000000..24bd3c580e + assertFalse(shouldUpdate(100, 1_000, 100, null)) + } +} +-- +2.47.3 + diff --git a/tools/activity_theme.py b/tools/activity_theme.py new file mode 100644 index 0000000..f528887 --- /dev/null +++ b/tools/activity_theme.py @@ -0,0 +1,83 @@ +# Copyright 2026 Vesper contributors +# SPDX-License-Identifier: AGPL-3.0-only +"""Theme hooks for activities renamed or replaced across supported Signal tags.""" +from pathlib import Path + + +def _replace_once(source: str, old: str, new: str) -> str: + if source.count(old) != 1: + raise RuntimeError(f"Unexpected activity lifecycle context: {old!r}") + return source.replace(old, new, 1) + + +def transform_activity_themes(checkout: Path) -> int: + root = checkout / "app/src/main/java/org/thoughtcrime/securesms" + changed = 0 + # Signal 8.32 renamed this host; keep both layouts and navigation policies. + for name in ("components/settings/DSLSettingsActivity.kt", "components/NavGraphHostActivity.kt"): + path = root / name + if not path.exists(): + continue + source = path.read_text() + if "private var themeAppliedBeforeInflation = false" in source: + continue + source = _replace_once(source, " dynamicTheme.onCreate(this)\n", "") + source = _replace_once(source, " override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {", ''' private var themeAppliedBeforeInflation = false + + override fun onPreCreate() { + super.onPreCreate() + dynamicTheme.onCreate(this) + themeAppliedBeforeInflation = true + } + + override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {''') + source = _replace_once(source, " enableSettingsEdgeToEdge()\n", ''' enableSettingsEdgeToEdge() + check(themeAppliedBeforeInflation) { + "The dynamic settings theme must be applied before inflating views." + } +''') + # Preserve the exact previously patched layout (one blank line). + source = source.replace("\n\n\n onBackPressedDispatcher", "\n\n onBackPressedDispatcher") + path.write_text(source) + changed += 1 + # The old picker was replaced by a passphrase-gated Compose activity. + # Never resurrect the removed activity or change the new result contract. + legacy = root / "scribbles/ImageEditorStickerSelectActivity.java" + modern = root / "stickers/search/StickerSearchActivity.kt" + for path in (legacy, modern): + if not path.exists(): + continue + source = path.read_text() + if "dynamicTheme.onCreate(this)" in source: + continue + if path == legacy: + source = _replace_once(source, "import org.thoughtcrime.securesms.util.ViewUtil;", "import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;\nimport org.thoughtcrime.securesms.util.DynamicTheme;\nimport org.thoughtcrime.securesms.util.ViewUtil;") + anchor = ' public static final String EXTRA_FEATURE_STICKER = "imageEditor.featureSticker";' + source = _replace_once(source, anchor, anchor + ''' + private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); + + @Override + protected void onResume() { + super.onResume(); + dynamicTheme.onResume(this); + }''') + source = _replace_once(source, " EdgeToEdge.enable(this);", " dynamicTheme.onCreate(this);\n EdgeToEdge.enable(this);") + else: + source = _replace_once(source, "import org.thoughtcrime.securesms.PassphraseRequiredActivity", "import org.thoughtcrime.securesms.util.DynamicMediaPreviewTheme\nimport org.thoughtcrime.securesms.PassphraseRequiredActivity") + anchor = "class StickerSearchActivity : PassphraseRequiredActivity() {" + source = _replace_once(source, anchor, anchor + ''' + + private val dynamicTheme = DynamicMediaPreviewTheme() + + override fun onPreCreate() { + super.onPreCreate() + dynamicTheme.onCreate(this) + } + + override fun onResume() { + super.onResume() + dynamicTheme.onResume(this) + }''') + path.write_text(source) + changed += 1 + return changed diff --git a/tools/test_activity_theme.py b/tools/test_activity_theme.py new file mode 100644 index 0000000..e9d9c33 --- /dev/null +++ b/tools/test_activity_theme.py @@ -0,0 +1,94 @@ +# Copyright 2026 Vesper contributors +# SPDX-License-Identifier: AGPL-3.0-only +"""Cross-version lifecycle hooks; the real source is also checked after replay.""" +from pathlib import Path +import tempfile +import unittest +from activity_theme import transform_activity_themes + + +class ActivityThemeTest(unittest.TestCase): + def test_settings_theme_precedes_inflation_for_both_names(self): + for name, layout in (("components/settings/DSLSettingsActivity.kt", "dsl_settings_activity"), + ("components/NavGraphHostActivity.kt", "nav_graph_host_activity")): + with self.subTest(name=name), tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + path = root / "app/src/main/java/org/thoughtcrime/securesms" / name + path.parent.mkdir(parents=True) + path.write_text('''package example +class Activity { + override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { + enableSettingsEdgeToEdge() + + setContentView(R.layout.LAYOUT) + val navGraphId = resolveNavGraphId() + dynamicTheme.onCreate(this) + + onBackPressedDispatcher.addCallback(this, OnBackPressed()) + } +} +'''.replace("LAYOUT", layout)) + self.assertEqual(transform_activity_themes(root), 1) + source = path.read_text() + self.assertEqual(source.count("dynamicTheme.onCreate(this)"), 1) + self.assertLess(source.index("dynamicTheme.onCreate(this)"), source.index("setContentView")) + self.assertIn("super.onPreCreate()", source) + self.assertIn("val navGraphId = resolveNavGraphId()", source) + self.assertIn("check(themeAppliedBeforeInflation)", source) + self.assertEqual(transform_activity_themes(root), 0) + self.assertEqual(source, path.read_text()) + + def test_sticker_picker_hooks_preserve_each_upstream_lifecycle(self): + for name, source, creation in ( + ("scribbles/ImageEditorStickerSelectActivity.java", '''package example; +import org.thoughtcrime.securesms.util.ViewUtil; +public final class ImageEditorStickerSelectActivity extends AppCompatActivity { + public static final String EXTRA_FEATURE_STICKER = "imageEditor.featureSticker"; + protected void onCreate(@Nullable Bundle savedInstanceState) { + EdgeToEdge.enable(this); + super.onCreate(savedInstanceState); + } +} +''', " EdgeToEdge.enable(this);"), + ("stickers/search/StickerSearchActivity.kt", '''package example +import org.thoughtcrime.securesms.PassphraseRequiredActivity +class StickerSearchActivity : PassphraseRequiredActivity() { + override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { + super.onCreate(savedInstanceState, ready) + SignalTheme(isDarkMode = true) { ImageEditorStickerSearch(onResult = ::finishWithResult) } + } +} +''', " override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {"), + ): + with self.subTest(name=name), tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + path = root / "app/src/main/java/org/thoughtcrime/securesms" / name + path.parent.mkdir(parents=True) + path.write_text(source) + self.assertEqual(transform_activity_themes(root), 1) + result = path.read_text() + self.assertEqual(result.count("dynamicTheme.onCreate(this)"), 1) + self.assertEqual(result.count("dynamicTheme.onResume(this)"), 1) + self.assertLess(result.index("dynamicTheme.onCreate(this)"), result.index(creation)) + if name.endswith(".kt"): + self.assertIn("DynamicMediaPreviewTheme()", result) + self.assertIn("SignalTheme(isDarkMode = true)", result) + self.assertIn("super.onPreCreate()", result) + else: + self.assertIn("new DynamicNoActionBarTheme()", result) + self.assertEqual(transform_activity_themes(root), 0) + self.assertEqual(result, path.read_text()) + + def test_unknown_settings_lifecycle_fails_without_writing(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + path = root / "app/src/main/java/org/thoughtcrime/securesms/components/NavGraphHostActivity.kt" + path.parent.mkdir(parents=True) + path.write_text("upstream lifecycle changed") + with self.assertRaises(RuntimeError): + transform_activity_themes(root) + self.assertEqual(path.read_text(), "upstream lifecycle changed") + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/test_materialised_theme.py b/tools/test_materialised_theme.py index eb69961..f1c705f 100644 --- a/tools/test_materialised_theme.py +++ b/tools/test_materialised_theme.py @@ -15,6 +15,34 @@ class MaterialisedThemeContractTest(unittest.TestCase): + def test_settings_host_theme_precedes_inflation(self) -> None: + paths = [SOURCE / "components/settings/DSLSettingsActivity.kt", SOURCE / "components/NavGraphHostActivity.kt"] + sources = [path.read_text() for path in paths if path.exists()] + self.assertEqual(len(sources), 1) + source = sources[0] + self.assertEqual(source.count("dynamicTheme.onCreate(this)"), 1) + self.assertLess(source.index("dynamicTheme.onCreate(this)"), source.index("setContentView(")) + self.assertIn("super.onPreCreate()", source) + self.assertIn("check(themeAppliedBeforeInflation)", source) + self.assertIn("val navGraphId = resolveNavGraphId()", source) + + def test_sticker_picker_retains_theme_and_upstream_security(self) -> None: + legacy = SOURCE / "scribbles/ImageEditorStickerSelectActivity.java" + modern = SOURCE / "stickers/search/StickerSearchActivity.kt" + self.assertNotEqual(legacy.exists(), modern.exists()) + source = (legacy if legacy.exists() else modern).read_text() + self.assertEqual(source.count("dynamicTheme.onCreate(this)"), 1) + self.assertEqual(source.count("dynamicTheme.onResume(this)"), 1) + if modern.exists(): + self.assertIn("StickerSearchActivity : PassphraseRequiredActivity()", source) + self.assertIn("DynamicMediaPreviewTheme()", source) + self.assertIn("SignalTheme(isDarkMode = true)", source) + self.assertIn("ImageEditorStickerSearch(onResult = ::finishWithResult)", source) + self.assertLess(source.index("dynamicTheme.onCreate(this)"), source.index("setContent {")) + else: + self.assertIn("new DynamicNoActionBarTheme()", source) + self.assertLess(source.index("dynamicTheme.onCreate(this)"), source.index("super.onCreate(savedInstanceState)")) + def test_conversation_retains_material_and_upstream_compose_colors(self) -> None: source = (SOURCE / "conversation/v2/ConversationFragment.kt").read_text() self.assertEqual(source.count('import com.google.android.material.R as MaterialR\n'), 1) diff --git a/tools/transform.py b/tools/transform.py index a3f2e7c..d0cf634 100755 --- a/tools/transform.py +++ b/tools/transform.py @@ -784,6 +784,8 @@ def main() -> int: manifest = load_string_manifest(STRING_MANIFEST_PATH) mappings = load_color_mappings(COLORS_PATH) + from activity_theme import transform_activity_themes + transform_activity_themes(checkout) transform_gradle(checkout, config) string_files, missing_strings = transform_strings(checkout, config, manifest) themed_resource_files = transform_theme_color_references(checkout)