diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0af4afb4ef..0b626c8bb1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -508,7 +508,6 @@ dependencies { implementation(project(":core-ui")) implementation(libs.androidx.fragment.ktx) - implementation(libs.androidx.fragment.compose) implementation(libs.androidx.appcompat) { version { strictly("1.6.1") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 25f667073b..5be306408a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -697,6 +697,11 @@ android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:exported="false"/> + + currentSelection; - private boolean isMulti; - private boolean canSelectSelf; - private boolean resetPositionOnCommit = false; + @Nullable private NewConversationCallback newConversationCallback; + @Nullable private FindByCallback findByCallback; + @Nullable private NewCallCallback newCallCallback; + @Nullable private ScrollCallback scrollCallback; + @Nullable private OnItemLongClickListener onItemLongClickListener; + private SelectionLimits selectionLimit = SelectionLimits.NO_LIMITS; + private Set currentSelection; + private boolean isMulti; + private boolean canSelectSelf; + private boolean resetPositionOnCommit = false; private ListClickListener listClickListener = new ListClickListener(); @Nullable private SwipeRefreshLayout.OnRefreshListener onRefreshListener; @@ -161,7 +161,7 @@ public final class ContactSelectionListFragment extends LoggingFragment { } if (context instanceof FindByCallback) { - findByCallback = (FindByCallback) context; + showFindByUsernameAndPhoneOptions((FindByCallback) context); } if (context instanceof NewCallCallback) { @@ -177,11 +177,11 @@ public final class ContactSelectionListFragment extends LoggingFragment { } if (getParentFragment() instanceof OnContactSelectedListener) { - onContactSelectedListener = (OnContactSelectedListener) getParentFragment(); + setOnContactSelectedListener((OnContactSelectedListener) getParentFragment()); } if (context instanceof OnContactSelectedListener) { - onContactSelectedListener = (OnContactSelectedListener) context; + setOnContactSelectedListener((OnContactSelectedListener) context); } if (context instanceof OnSelectionLimitReachedListener) { @@ -209,6 +209,14 @@ public final class ContactSelectionListFragment extends LoggingFragment { } } + public void showFindByUsernameAndPhoneOptions(@Nullable FindByCallback callback) { + this.findByCallback = callback; + } + + public void setOnContactSelectedListener(@Nullable OnContactSelectedListener listener) { + this.onContactSelectedListener = listener; + } + @Override public void onActivityCreated(Bundle icicle) { super.onActivityCreated(icicle); @@ -221,7 +229,7 @@ public final class ContactSelectionListFragment extends LoggingFragment { super.onStart(); if (hasContactsPermissions(requireContext()) && !TextSecurePreferences.hasSuccessfullyRetrievedDirectory(getActivity())) { - handleContactPermissionGranted(); + handleContactPermissionGranted(); } else { requireActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); contactSearchMediator.refresh(); @@ -232,13 +240,13 @@ public final class ContactSelectionListFragment extends LoggingFragment { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false); - emptyText = view.findViewById(android.R.id.empty); - recyclerView = view.findViewById(R.id.recycler_view); - swipeRefresh = view.findViewById(R.id.swipe_refresh); - fastScroller = view.findViewById(R.id.fast_scroller); - chipRecycler = view.findViewById(R.id.chipRecycler); - constraintLayout = view.findViewById(R.id.container); - headerActionView = view.findViewById(R.id.header_action); + emptyText = view.findViewById(android.R.id.empty); + recyclerView = view.findViewById(R.id.recycler_view); + swipeRefresh = view.findViewById(R.id.swipe_refresh); + fastScroller = view.findViewById(R.id.fast_scroller); + chipRecycler = view.findViewById(R.id.chipRecycler); + constraintLayout = view.findViewById(R.id.container); + headerActionView = view.findViewById(R.id.header_action); final LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext()); @@ -441,7 +449,7 @@ public final class ContactSelectionListFragment extends LoggingFragment { @Override public void onDestroyView() { super.onDestroyView(); - constraintLayout = null; + constraintLayout = null; onRefreshListener = null; } @@ -723,7 +731,7 @@ public final class ContactSelectionListFragment extends LoggingFragment { SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> { return UsernameRepository.fetchAciForUsername(UsernameUtil.sanitizeUsernameFromSearch(username)); - }, result -> { + }, result -> { loadingDialog.dismiss(); // TODO Could be more specific with errors @@ -756,10 +764,10 @@ public final class ContactSelectionListFragment extends LoggingFragment { selectedContact.getNumber(), Optional.empty(), allowed -> { - if (allowed) { - markContactSelected(selectedContact); - } - }); + if (allowed) { + markContactSelected(selectedContact); + } + }); } else { markContactSelected(selectedContact); } @@ -913,9 +921,10 @@ public final class ContactSelectionListFragment extends LoggingFragment { builder.setQuery(contactSearchState.getQuery()); if ((newConversationCallback != null || findByCallback != null) && - !hasContactsPermissions(requireContext()) && + !hasContactsPermissions(requireContext()) && !SignalStore.uiHints().getDismissedContactsPermissionBanner() && - !hasQuery) { + !hasQuery) + { builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.FIND_CONTACTS_BANNER.getCode()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterView.java b/app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterView.java index 11fff39882..3225dbfd9e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ContactFilterView.java @@ -25,8 +25,13 @@ import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.ViewUtil; +/** + * A search input field for finding recipients. + *

+ * In compose, use RecipientSearchField instead. + */ public final class ContactFilterView extends FrameLayout { - private OnFilterChangedListener listener; + private OnFilterChangedListener listener; private final EditText searchText; private final AnimatingToggle toggle; diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/compose/ScreenTitlePane.kt b/app/src/main/java/org/thoughtcrime/securesms/components/compose/ScreenTitlePane.kt new file mode 100644 index 0000000000..81c2338fcd --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/components/compose/ScreenTitlePane.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.components.compose + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.thoughtcrime.securesms.window.WindowSizeClass + +/** + * Displays the screen title for split-pane UIs on tablets and foldable devices. + */ +@Composable +fun ScreenTitlePane( + title: String, + modifier: Modifier = Modifier +) { + val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + + Text( + text = title, + style = MaterialTheme.typography.headlineLarge, + color = MaterialTheme.colorScheme.onSurface, + modifier = modifier + .padding( + start = if (windowSizeClass.isExtended()) 80.dp else 20.dp, + end = 20.dp, + top = 12.dp, + bottom = 12.dp + ) + ) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationActivityV2.kt new file mode 100644 index 0000000000..2200949ca3 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationActivityV2.kt @@ -0,0 +1,152 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.conversation + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import org.signal.core.ui.compose.AllDevicePreviews +import org.signal.core.ui.compose.Previews +import org.signal.core.ui.compose.Scaffolds +import org.signal.core.ui.compose.theme.SignalTheme +import org.thoughtcrime.securesms.PassphraseRequiredActivity +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.components.compose.ScreenTitlePane +import org.thoughtcrime.securesms.util.viewModel +import org.thoughtcrime.securesms.window.AppScaffoldWithTopBar +import org.thoughtcrime.securesms.window.WindowSizeClass +import org.thoughtcrime.securesms.window.rememberAppScaffoldNavigator + +/** + * Allows the user to start a new conversation by selecting a recipient. + * + * A modernized compose-based replacement for [org.thoughtcrime.securesms.NewConversationActivity]. + */ +class NewConversationActivityV2 : PassphraseRequiredActivity() { + companion object { + @JvmStatic + fun createIntent(context: Context): Intent = Intent(context, NewConversationActivityV2::class.java) + } + + private val viewModel by viewModel { NewConversationViewModel() } + + override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { + super.onCreate(savedInstanceState, ready) + + setContent { + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + + SignalTheme { + NewConversationScreen( + uiState = uiState, + callbacks = object : Callbacks { + override fun onBackPressed() = onBackPressedDispatcher.onBackPressed() + } + ) + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3AdaptiveApi::class) +@Composable +private fun NewConversationScreen( + uiState: NewConversationUiState, + callbacks: Callbacks +) { + val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val isSplitPane = windowSizeClass.isSplitPane(forceSplitPaneOnCompactLandscape = uiState.forceSplitPaneOnCompactLandscape) + + AppScaffoldWithTopBar( + topBarContent = { + Scaffolds.DefaultTopAppBar( + title = if (!isSplitPane) stringResource(R.string.NewConversationActivity__new_message) else "", + titleContent = { _, title -> Text(text = title, style = MaterialTheme.typography.titleLarge) }, + navigationIcon = ImageVector.vectorResource(R.drawable.symbol_arrow_start_24), + navigationContentDescription = stringResource(R.string.DefaultTopAppBar__navigate_up_content_description), + onNavigationClick = callbacks::onBackPressed + ) + }, + listContent = { + if (isSplitPane) { + ScreenTitlePane( + title = stringResource(R.string.NewConversationActivity__new_message), + modifier = Modifier.fillMaxSize() + ) + } else { + DetailPaneContent() + } + }, + + detailContent = { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxSize() + ) { + DetailPaneContent( + modifier = Modifier + .widthIn(max = windowSizeClass.detailPaneMaxContentWidth) + ) + } + }, + + navigator = rememberAppScaffoldNavigator( + isSplitPane = isSplitPane + ) + ) +} + +private interface Callbacks { + fun onBackPressed() + + object Empty : Callbacks { + override fun onBackPressed() = Unit + } +} + +@Composable +private fun DetailPaneContent( + modifier: Modifier = Modifier +) { + RecipientPicker( + showFindByUsernameAndPhoneOptions = true, + callbacks = RecipientPickerCallbacks.Empty, // TODO(jeffrey) implement callbacks + modifier = modifier + .fillMaxSize() + .padding(vertical = 12.dp) + ) +} + +@AllDevicePreviews +@Composable +private fun NewConversationScreenPreview() { + Previews.Preview { + NewConversationScreen( + uiState = NewConversationUiState( + forceSplitPaneOnCompactLandscape = false + ), + callbacks = Callbacks.Empty + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationViewModel.kt new file mode 100644 index 0000000000..1720d95707 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/NewConversationViewModel.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.conversation + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import org.thoughtcrime.securesms.keyvalue.SignalStore + +class NewConversationViewModel : ViewModel() { + private val _uiState = MutableStateFlow(NewConversationUiState()) + val uiState: StateFlow = _uiState.asStateFlow() +} + +data class NewConversationUiState( + val forceSplitPaneOnCompactLandscape: Boolean = SignalStore.internal.forceSplitPaneOnCompactLandscape +) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/RecipientPicker.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/RecipientPicker.kt new file mode 100644 index 0000000000..7cc81a7727 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/RecipientPicker.kt @@ -0,0 +1,155 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.conversation + +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.fragment.compose.rememberFragmentState +import org.signal.core.ui.compose.DayNightPreviews +import org.signal.core.ui.compose.Fragments +import org.thoughtcrime.securesms.ContactSelectionListFragment +import org.thoughtcrime.securesms.components.ContactFilterView +import org.thoughtcrime.securesms.recipients.RecipientId + +/** + * Provides a recipient search and selection UI. + */ +@Composable +fun RecipientPicker( + showFindByUsernameAndPhoneOptions: Boolean, + callbacks: RecipientPickerCallbacks, + modifier: Modifier = Modifier +) { + var searchQuery by rememberSaveable { mutableStateOf("") } + + Column( + modifier = modifier + ) { + RecipientSearchField( + onFilterChanged = { filter -> + searchQuery = filter + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) + + RecipientSearchResultsList( + searchQuery = searchQuery, + showFindByUsernameAndPhoneOptions = showFindByUsernameAndPhoneOptions, + callbacks = callbacks, + modifier = Modifier + .fillMaxSize() + .padding(top = 8.dp) + ) + } +} + +/** + * A search input field for finding recipients. + * + * Intended to be a compose-based replacement for [ContactFilterView]. + */ +@Composable +private fun RecipientSearchField( + onFilterChanged: (String) -> Unit, + @StringRes hintText: Int? = null, + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val wrappedView = remember { + ContactFilterView(context, null, 0).apply { + hintText?.let { setHint(it) } + } + } + + DisposableEffect(onFilterChanged) { + wrappedView.setOnFilterChangedListener { filter -> onFilterChanged(filter) } + onDispose { + wrappedView.setOnFilterChangedListener(null) + } + } + + AndroidView( + factory = { wrappedView }, + modifier = modifier + ) +} + +@Composable +private fun RecipientSearchResultsList( + searchQuery: String, + showFindByUsernameAndPhoneOptions: Boolean, + callbacks: RecipientPickerCallbacks, + modifier: Modifier = Modifier +) { + val fragmentState = rememberFragmentState() + var currentFragment by remember { mutableStateOf(null) } + + Fragments.Fragment( + fragmentState = fragmentState, + onUpdate = { fragment -> + currentFragment = fragment + currentFragment?.view?.setPadding(0, 0, 0, 0) + + if (showFindByUsernameAndPhoneOptions) { + fragment.showFindByUsernameAndPhoneOptions(object : ContactSelectionListFragment.FindByCallback { + override fun onFindByUsername() = callbacks.onFindByUsernameClicked() + override fun onFindByPhoneNumber() = callbacks.onFindByPhoneNumberClicked() + }) + } + }, + modifier = modifier + ) + + var previousQueryText by rememberSaveable { mutableStateOf("") } + LaunchedEffect(searchQuery) { + if (previousQueryText != searchQuery) { + if (searchQuery.isNotBlank()) { + currentFragment?.setQueryFilter(searchQuery) + } else { + currentFragment?.resetQueryFilter() + } + previousQueryText = searchQuery + } + } +} + +@DayNightPreviews +@Composable +private fun RecipientPickerPreview() { + RecipientPicker( + showFindByUsernameAndPhoneOptions = true, + callbacks = RecipientPickerCallbacks.Empty + ) +} + +interface RecipientPickerCallbacks { + fun onFindByUsernameClicked() + fun onFindByPhoneNumberClicked() + fun onRecipientClicked(id: RecipientId) + + object Empty : RecipientPickerCallbacks { + override fun onFindByUsernameClicked() = Unit + override fun onFindByPhoneNumberClicked() = Unit + override fun onRecipientClicked(id: RecipientId) = Unit + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffold.kt b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffold.kt index c7cef864a9..66c03831a0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffold.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffold.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.window.core.ExperimentalWindowCoreApi @@ -86,6 +87,12 @@ enum class WindowSizeClass( EXTENDED_PORTRAIT(Navigation.RAIL), EXTENDED_LANDSCAPE(Navigation.RAIL); + val listPaneDefaultPreferredWidth: Dp + get() = if (isExtended()) 416.dp else 316.dp + + val detailPaneMaxContentWidth: Dp = 624.dp + val horizontalPartitionDefaultSpacerSize: Dp = 12.dp + fun isCompact(): Boolean = this == COMPACT_PORTRAIT || this == COMPACT_LANDSCAPE fun isMedium(): Boolean = this == MEDIUM_PORTRAIT || this == MEDIUM_LANDSCAPE fun isExtended(): Boolean = this == EXTENDED_PORTRAIT || this == EXTENDED_LANDSCAPE @@ -93,8 +100,11 @@ enum class WindowSizeClass( fun isLandscape(): Boolean = this == COMPACT_LANDSCAPE || this == MEDIUM_LANDSCAPE || this == EXTENDED_LANDSCAPE fun isPortrait(): Boolean = !isLandscape() - fun isSplitPane(): Boolean { - return if (isLargeScreenSupportEnabled() && SignalStore.internal.forceSplitPaneOnCompactLandscape) { + @JvmOverloads + fun isSplitPane( + forceSplitPaneOnCompactLandscape: Boolean = SignalStore.internal.forceSplitPaneOnCompactLandscape + ): Boolean { + return if (isLargeScreenSupportEnabled() && forceSplitPaneOnCompactLandscape) { this != COMPACT_PORTRAIT } else { this.navigation != Navigation.BAR diff --git a/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldNavigator.kt b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldNavigator.kt index 87681368b7..e59efe3dd3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldNavigator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldNavigator.kt @@ -19,7 +19,9 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.unit.Dp +import org.thoughtcrime.securesms.keyvalue.SignalStore /** * AppScaffoldNavigator wraps a delegate navigator (such as the value returned by [rememberThreePaneScaffoldNavigatorDelegate] @@ -85,9 +87,12 @@ open class AppScaffoldNavigator @RememberInComposition constructor(private va @OptIn(ExperimentalMaterial3AdaptiveApi::class) @Composable fun rememberAppScaffoldNavigator( - isSplitPane: Boolean, - horizontalPartitionSpacerSize: Dp, - defaultPanePreferredWidth: Dp + windowSizeClass: WindowSizeClass = WindowSizeClass.rememberWindowSizeClass(), + isSplitPane: Boolean = windowSizeClass.isSplitPane( + forceSplitPaneOnCompactLandscape = if (LocalInspectionMode.current) false else SignalStore.internal.forceSplitPaneOnCompactLandscape + ), + horizontalPartitionSpacerSize: Dp = windowSizeClass.horizontalPartitionDefaultSpacerSize, + defaultPanePreferredWidth: Dp = windowSizeClass.listPaneDefaultPreferredWidth ): AppScaffoldNavigator { val delegate = rememberThreePaneScaffoldNavigatorDelegate( isSplitPane, diff --git a/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldWithTopBar.kt b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldWithTopBar.kt new file mode 100644 index 0000000000..32d8426c2f --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/window/AppScaffoldWithTopBar.kt @@ -0,0 +1,142 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.window + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.layout.PaneExpansionState +import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldScope +import androidx.compose.material3.adaptive.layout.rememberPaneExpansionState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign +import org.signal.core.ui.compose.AllDevicePreviews +import org.signal.core.ui.compose.Previews +import org.signal.core.ui.compose.Scaffolds +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.keyvalue.SignalStore + +/** + * Wraps [AppScaffold], adding a top app bar that spans across both the list and detail panes. + */ +@OptIn(ExperimentalMaterial3AdaptiveApi::class) +@Composable +fun AppScaffoldWithTopBar( + navigator: AppScaffoldNavigator = rememberAppScaffoldNavigator(), + topBarContent: @Composable () -> Unit = {}, + detailContent: @Composable () -> Unit = {}, + navRailContent: @Composable () -> Unit = {}, + bottomNavContent: @Composable () -> Unit = {}, + paneExpansionState: PaneExpansionState = rememberPaneExpansionState(), + paneExpansionDragHandle: (@Composable ThreePaneScaffoldScope.(PaneExpansionState) -> Unit)? = null, + animatorFactory: AppScaffoldAnimationStateFactory = AppScaffoldAnimationStateFactory.Default, + listContent: @Composable () -> Unit +) { + val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val isSplitPane = windowSizeClass.isSplitPane( + forceSplitPaneOnCompactLandscape = if (LocalInspectionMode.current) false else SignalStore.internal.forceSplitPaneOnCompactLandscape + ) + + if (isSplitPane) { + Column { + topBarContent() + + AppScaffold( + navigator = navigator, + detailContent = detailContent, + navRailContent = navRailContent, + bottomNavContent = bottomNavContent, + paneExpansionState = paneExpansionState, + paneExpansionDragHandle = paneExpansionDragHandle, + animatorFactory = animatorFactory, + listContent = listContent + ) + } + } else { + AppScaffold( + navigator = navigator, + detailContent = detailContent, + navRailContent = navRailContent, + bottomNavContent = bottomNavContent, + paneExpansionState = paneExpansionState, + paneExpansionDragHandle = paneExpansionDragHandle, + animatorFactory = animatorFactory, + listContent = { + Scaffold(topBar = topBarContent) { paddingValues -> + Box(modifier = Modifier.padding(paddingValues)) { + listContent() + } + } + } + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3AdaptiveApi::class) +@AllDevicePreviews +@Composable +private fun AppScaffoldWithTopBarPreview() { + Previews.Preview { + val windowSizeClass = WindowSizeClass.rememberWindowSizeClass() + val isSplitPane = windowSizeClass.isSplitPane(forceSplitPaneOnCompactLandscape = false) + + AppScaffoldWithTopBar( + navigator = rememberAppScaffoldNavigator(), + + topBarContent = { + Scaffolds.DefaultTopAppBar( + title = if (!isSplitPane) stringResource(R.string.NewConversationActivity__new_message) else "", + titleContent = { _, title -> Text(text = title, style = MaterialTheme.typography.titleLarge) }, + navigationIcon = ImageVector.vectorResource(R.drawable.symbol_arrow_start_24), + navigationContentDescription = "", + onNavigationClick = { } + ) + }, + + listContent = { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxSize() + .background(color = Color.Red) + ) { + Text( + text = "ListContent\n$windowSizeClass", + textAlign = TextAlign.Center + ) + } + }, + + detailContent = { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxSize() + .background(color = Color.Blue) + ) { + Text( + text = "DetailContent", + textAlign = TextAlign.Center + ) + } + } + ) + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fd08a08182..8031391353 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5926,8 +5926,8 @@ %1$s are not Signal users - - Search name or number + + Name, username or number ยท %1$s diff --git a/core-ui/build.gradle.kts b/core-ui/build.gradle.kts index 3aee2cfda5..a13407fe06 100644 --- a/core-ui/build.gradle.kts +++ b/core-ui/build.gradle.kts @@ -29,4 +29,5 @@ dependencies { api(libs.androidx.compose.material3.adaptive.navigation) api(libs.androidx.compose.ui.tooling.preview) debugApi(libs.androidx.compose.ui.tooling.core) + api(libs.androidx.fragment.compose) } diff --git a/core-ui/src/main/java/org/signal/core/ui/compose/Fragments.kt b/core-ui/src/main/java/org/signal/core/ui/compose/Fragments.kt new file mode 100644 index 0000000000..f65ffd3a39 --- /dev/null +++ b/core-ui/src/main/java/org/signal/core/ui/compose/Fragments.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.signal.core.ui.compose + +import android.os.Bundle +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.fragment.app.Fragment +import androidx.fragment.compose.AndroidFragment +import androidx.fragment.compose.FragmentState +import androidx.fragment.compose.rememberFragmentState +import org.signal.core.ui.compose.Fragments.Fragment + +object Fragments { + /** + * Wraps an [Fragment], displaying the fragment at runtime or a placeholder in compose previews to avoid rendering errors that occur when + * using [Fragment] in @Preview composables. + */ + @Composable + inline fun Fragment( + modifier: Modifier = Modifier, + fragmentState: FragmentState = rememberFragmentState(), + arguments: Bundle = Bundle.EMPTY, + noinline onUpdate: (T) -> Unit = { } + ) { + if (!LocalInspectionMode.current) { + AndroidFragment(clazz = T::class.java, modifier, fragmentState, arguments, onUpdate) + } else { + Text( + text = "[${T::class.simpleName}]", + style = MaterialTheme.typography.bodyLarge, + modifier = modifier + .fillMaxSize() + .background(Color.Gray) + .wrapContentSize(Alignment.Center) + ) + } + } + + /** + * Wraps an [Fragment], displaying the fragment at runtime or a placeholder in compose previews to avoid rendering errors that occur when + * using [Fragment] in @Preview composables. + */ + @Composable + fun Fragment( + clazz: Class, + modifier: Modifier = Modifier, + fragmentState: FragmentState = rememberFragmentState(), + arguments: Bundle = Bundle.EMPTY, + onUpdate: (T) -> Unit = { } + ) { + if (!LocalInspectionMode.current) { + AndroidFragment(clazz = clazz, modifier, fragmentState, arguments, onUpdate) + } else { + Text( + text = "[${clazz.simpleName}]", + style = MaterialTheme.typography.bodyLarge, + modifier = modifier + .fillMaxSize() + .background(Color.Gray) + .wrapContentSize(Alignment.Center) + ) + } + } +} diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 027523ef4f..54333acda7 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -25,108 +25,158 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -145,6 +195,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -153,9 +205,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -166,275 +222,414 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -445,14 +640,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -463,14 +664,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -481,17 +688,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -505,9 +720,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -521,17 +740,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -545,9 +772,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -561,9 +792,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -577,9 +812,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -593,14 +832,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -614,9 +859,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -630,9 +879,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -646,17 +899,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -670,9 +931,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -686,9 +951,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -702,9 +971,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -715,6 +988,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -728,9 +1003,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -744,9 +1023,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -760,9 +1043,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -776,9 +1063,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -792,9 +1083,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -808,9 +1103,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -824,86 +1123,128 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -930,9 +1271,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -946,9 +1291,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -962,9 +1311,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -978,9 +1331,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -994,9 +1351,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1010,9 +1371,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1034,9 +1399,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1058,14 +1427,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -1087,9 +1462,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1103,17 +1482,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1127,6 +1514,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -1137,9 +1526,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1153,9 +1546,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1169,17 +1566,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1193,9 +1598,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1209,9 +1618,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1225,17 +1638,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1249,9 +1670,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1265,9 +1690,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1281,9 +1710,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1297,9 +1730,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1313,9 +1750,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1329,9 +1770,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1345,9 +1790,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1361,9 +1810,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1377,17 +1830,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1401,9 +1862,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1417,9 +1882,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1433,9 +1902,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1449,9 +1922,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1465,9 +1942,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1481,9 +1962,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1497,9 +1982,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1513,9 +2002,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1529,9 +2022,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1545,9 +2042,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1561,9 +2062,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1577,17 +2082,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1601,9 +2114,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1617,9 +2134,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1633,17 +2154,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -1657,9 +2186,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1673,9 +2206,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1689,161 +2226,247 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1859,6 +2482,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -1874,9 +2499,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -1890,241 +2519,353 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2138,37 +2879,53 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + @@ -2182,30 +2939,44 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + @@ -2219,43 +2990,63 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + @@ -2269,51 +3060,75 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + @@ -2327,46 +3142,68 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + @@ -2380,17 +3217,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -2399,6 +3244,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -2412,43 +3259,70 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2462,17 +3336,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -2481,6 +3363,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -2494,17 +3378,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -2513,6 +3405,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -2526,43 +3420,70 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2573,6 +3494,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -2581,9 +3504,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -2597,14 +3524,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -2618,6 +3551,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -2626,9 +3561,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -2642,9 +3581,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -2655,6 +3598,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -2671,22 +3616,32 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -2700,22 +3655,32 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -2729,6 +3694,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -2742,25 +3709,37 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + @@ -2771,6 +3750,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -2779,9 +3760,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -2795,14 +3780,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -2816,6 +3807,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -2824,9 +3817,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -2840,9 +3837,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -2851,11 +3852,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -2869,35 +3874,51 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + @@ -2914,6 +3935,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -2922,12 +3945,18 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -2944,20 +3973,30 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -2979,9 +4018,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -2995,19 +4038,27 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3016,6 +4067,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -3032,19 +4085,27 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3053,6 +4114,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -3069,9 +4132,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -3080,6 +4147,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -3093,9 +4162,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -3114,6 +4187,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -3127,25 +4202,37 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + @@ -3162,14 +4249,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -3183,33 +4276,49 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + @@ -3226,14 +4335,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -3247,14 +4362,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -3268,9 +4389,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -3284,268 +4409,398 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3559,64 +4814,94 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3625,14 +4910,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -3646,14 +4937,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -3662,14 +4959,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -3678,17 +4981,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3710,17 +5021,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -3729,75 +5048,111 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3808,6 +5163,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -3818,11 +5175,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -3838,56 +5199,78 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + @@ -3898,11 +5281,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -3913,217 +5300,317 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4139,6 +5626,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -4154,16 +5643,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -4184,11 +5679,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4209,11 +5708,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4234,21 +5737,29 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -4269,6 +5780,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -4284,11 +5797,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4304,11 +5821,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4324,11 +5845,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4344,6 +5869,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -4359,11 +5886,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4379,11 +5910,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4399,11 +5934,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4452,17 +5991,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -4479,14 +6026,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -4503,11 +6056,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4523,6 +6080,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -4538,9 +6097,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4557,22 +6120,32 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -4589,14 +6162,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -4613,6 +6192,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -4628,14 +6209,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -4652,14 +6239,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -4676,14 +6269,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -4700,22 +6299,32 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -4737,32 +6346,46 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + @@ -4778,6 +6401,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -4793,6 +6418,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -4813,6 +6440,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -4833,6 +6462,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -4848,11 +6479,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4873,6 +6508,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -4893,6 +6530,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -4913,6 +6552,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -4943,11 +6584,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4963,11 +6608,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4983,9 +6632,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -4999,6 +6652,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -5014,9 +6669,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -5030,6 +6689,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -5045,9 +6706,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -5061,6 +6726,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -5076,9 +6743,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -5092,6 +6763,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -5107,9 +6780,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -5123,6 +6800,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -5138,9 +6817,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -5154,6 +6837,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -5169,9 +6854,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -5185,6 +6874,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -5195,9 +6886,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -5211,6 +6906,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -5226,9 +6923,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -5242,6 +6943,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -5275,9 +6978,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -5291,6 +6998,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -5306,6 +7015,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -5316,254 +7027,364 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5574,59 +7395,83 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + @@ -5637,46 +7482,64 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + @@ -5687,6 +7550,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -5697,9 +7562,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -5713,9 +7582,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -5726,6 +7599,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -5739,9 +7614,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -5755,248 +7634,350 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -6007,46 +7988,64 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + @@ -6060,386 +8059,568 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -6453,17 +8634,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -6477,49 +8666,73 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + @@ -6541,22 +8754,32 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -6578,298 +8801,436 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -6880,16 +9241,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -6900,16 +9267,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -6920,16 +9293,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -6940,16 +9319,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -6960,16 +9345,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -6980,21 +9371,29 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -7005,24 +9404,34 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -7036,9 +9445,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -7052,9 +9465,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -7068,9 +9485,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -7084,9 +9505,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -7100,9 +9525,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -7116,9 +9545,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -7132,9 +9565,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -7148,9 +9585,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -7164,9 +9605,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -7180,9 +9625,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -7196,9 +9645,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -7212,214 +9665,304 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7430,6 +9973,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7440,165 +9985,233 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7614,99 +10227,139 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7720,17 +10373,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -7746,9 +10407,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -7759,6 +10424,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7769,6 +10436,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7779,6 +10448,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7789,6 +10460,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7799,6 +10472,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7809,16 +10484,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -7829,6 +10510,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7839,6 +10522,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7849,6 +10534,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7859,6 +10546,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7869,6 +10558,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7879,6 +10570,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7889,16 +10582,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -7909,6 +10608,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7919,6 +10620,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -7932,14 +10635,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -7953,14 +10662,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -7971,17 +10686,25 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -7998,20 +10721,30 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -8025,14 +10758,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -8043,14 +10782,20 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -8064,9 +10809,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -8077,6 +10826,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -8087,11 +10838,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -8102,6 +10857,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -8112,16 +10869,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -8132,6 +10895,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -8142,6 +10907,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -8152,6 +10919,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -8162,26 +10931,36 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -8192,29 +10971,41 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + @@ -8225,16 +11016,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -8245,6 +11042,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -8260,6 +11059,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -8275,6 +11076,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -8290,6 +11093,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -8305,6 +11110,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -8318,9 +11125,13 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -8331,68 +11142,96 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -8409,6 +11248,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -8417,25 +11258,37 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + @@ -8444,25 +11297,35 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + @@ -8479,52 +11342,74 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + @@ -8535,11 +11420,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -8555,21 +11444,29 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + @@ -8580,16 +11477,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -8600,46 +11503,64 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + @@ -8650,41 +11571,57 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + @@ -8695,11 +11632,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -8710,11 +11651,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -8725,6 +11670,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -8735,172 +11682,254 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -8914,99 +11943,147 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -9020,25 +12097,37 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + @@ -9049,148 +12138,218 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -9206,6 +12365,8 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + @@ -9221,89 +12382,129 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -9314,29 +12515,41 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + @@ -9347,16 +12560,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -9367,11 +12586,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -9387,16 +12610,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -9407,11 +12636,15 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + @@ -9427,155 +12660,227 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -9586,16 +12891,22 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + @@ -9606,21 +12917,29 @@ https://docs.gradle.org/current/userguide/dependency_verification.html + + + + + + + +