diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryCustomScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryCustomScreen.kt
index 0c901db51d..89b08fc0f1 100644
--- a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryCustomScreen.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryCustomScreen.kt
@@ -50,6 +50,7 @@ import com.wire.android.ui.common.topappbar.NavigationIconType
import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar
import com.wire.android.ui.theme.WireTheme
import com.wire.android.util.ui.PreviewMultipleThemes
+import kotlinx.collections.immutable.toImmutableList
@WireNewConversationDestination(
navArgs = ChannelHistoryCustomArgs::class,
@@ -125,7 +126,7 @@ fun ChannelHistoryCustomScreenContent(
val items = ChannelHistoryType.On.Specific.AmountType.entries
val itemsNames = items.map { pluralStringResource(it.nameResId, amountState.text.toString().toIntOrNull() ?: 0) }
WireDropDown(
- items = itemsNames,
+ items = itemsNames.toImmutableList(),
defaultItemIndex = 0,
selectedItemIndex = items.indexOf(typeState),
label = stringResource(R.string.channel_history_custom_time_label),
diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/account/color/ChangeUserColorScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/account/color/ChangeUserColorScreen.kt
index 42474dd04f..0d67318c18 100644
--- a/app/src/main/kotlin/com/wire/android/ui/home/settings/account/color/ChangeUserColorScreen.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/account/color/ChangeUserColorScreen.kt
@@ -75,6 +75,7 @@ import com.wire.android.ui.theme.wireTypography
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.android.util.ui.UIText
import com.wire.kalium.logic.data.id.QualifiedID
+import kotlinx.collections.immutable.toImmutableList
@WireRootDestination(
style = SlideNavigationAnimation::class, // default should be SlideNavigationAnimation
@@ -173,7 +174,7 @@ fun ChangeUserColorContent(
val items = Accent.entries.filter { accent -> accent != Accent.Unknown }
WireDropDown(
- items = items.map { stringResource(it.resourceId()) },
+ items = items.map { stringResource(it.resourceId()) }.toImmutableList(),
defaultItemIndex = if (accentColor == Accent.Unknown) -1 else items.indexOf(accentColor),
label = null,
modifier = Modifier.padding(horizontal = MaterialTheme.wireDimensions.spacing16x),
diff --git a/app/src/main/kotlin/com/wire/android/ui/home/settings/account/email/updateEmail/ChangeEmailScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/settings/account/email/updateEmail/ChangeEmailScreen.kt
index 9380e4fef6..72441555fc 100644
--- a/app/src/main/kotlin/com/wire/android/ui/home/settings/account/email/updateEmail/ChangeEmailScreen.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/home/settings/account/email/updateEmail/ChangeEmailScreen.kt
@@ -142,6 +142,7 @@ fun ChangeEmailContent(
inputTransformation = InputTransformation.forceLowercase(),
state = computeEmailErrorState(state.flowState),
keyboardOptions = KeyboardOptions.DefaultEmailDone,
+ readOnly = state.flowState is ChangeEmailState.FlowState.Loading,
onKeyboardAction = { keyboardController?.hide() },
modifier = Modifier.padding(
horizontal = MaterialTheme.wireDimensions.spacing16x
@@ -191,8 +192,6 @@ private fun computeEmailErrorState(state: ChangeEmailState.FlowState): WireTextF
stringResource(id = R.string.settings_myaccount_email_generic_error)
)
- ChangeEmailState.FlowState.Loading -> WireTextFieldState.ReadOnly
-
else -> WireTextFieldState.Default
}
diff --git a/app/src/main/kotlin/com/wire/android/ui/newauthentication/login/BackendSelectorDropDown.kt b/app/src/main/kotlin/com/wire/android/ui/newauthentication/login/BackendSelectorDropDown.kt
index 963a526256..38b4fdc82b 100644
--- a/app/src/main/kotlin/com/wire/android/ui/newauthentication/login/BackendSelectorDropDown.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/newauthentication/login/BackendSelectorDropDown.kt
@@ -48,6 +48,7 @@ import com.wire.android.ui.common.WireDropDown
import com.wire.android.ui.common.dimensions
import com.wire.android.ui.common.textfield.WireTextField
import com.wire.android.ui.common.textfield.forceLowercase
+import kotlinx.collections.immutable.toImmutableList
@Composable
internal fun BackendSelectorDropDown() {
@@ -63,7 +64,7 @@ internal fun BackendSelectorDropDown() {
) {
WireDropDown(
modifier = Modifier.alpha(0.5f),
- items = backendConfigs.map { it.first },
+ items = backendConfigs.map { it.first }.toImmutableList(),
label = null,
autoUpdateSelection = false,
placeholder = "Change application backend",
diff --git a/app/src/main/kotlin/com/wire/android/ui/newauthentication/login/NewLoginContainer.kt b/app/src/main/kotlin/com/wire/android/ui/newauthentication/login/NewLoginContainer.kt
index f21f3e7d37..fe469ac5b4 100644
--- a/app/src/main/kotlin/com/wire/android/ui/newauthentication/login/NewLoginContainer.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/newauthentication/login/NewLoginContainer.kt
@@ -43,6 +43,7 @@ import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.zIndex
import com.wire.android.BuildConfig
import com.wire.android.ui.authentication.login.WireAuthBackgroundLayout
import com.wire.android.ui.common.bottomsheet.WireBottomSheetDefaults
@@ -82,6 +83,7 @@ fun NewAuthContainer(
Surface(
color = WireBottomSheetDefaults.WireSheetContainerColor,
shadowElevation = scrollState.rememberTopBarElevationState().value,
+ modifier = Modifier.zIndex(1f) // to ensure the header is above the column content when scrolled
) {
header()
}
diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/common/UserProfileInfo.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/common/UserProfileInfo.kt
index b72ea84c59..da01c5f7ea 100644
--- a/app/src/main/kotlin/com/wire/android/ui/userprofile/common/UserProfileInfo.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/common/UserProfileInfo.kt
@@ -147,7 +147,7 @@ fun UserProfileInfo(
targetState = userAvatarData to showPlaceholderIfNoAsset.value,
label = "UserProfileInfoAvatar"
) { (userAvatarData, showPlaceholderIfNoAsset) ->
- val onAvatarClickDescription = stringResource(R.string.content_description_change_it_label)
+ val onAvatarClickDescription = stringResource(commonR.string.content_description_change_it_label)
val contentDescription = if (editableState is EditableState.IsEditable) {
stringResource(R.string.content_description_self_profile_avatar)
} else {
diff --git a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt
index 7e53eee371..22a1a1377c 100644
--- a/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt
+++ b/app/src/main/kotlin/com/wire/android/ui/userprofile/self/SelfUserProfileScreen.kt
@@ -105,6 +105,7 @@ import com.wire.android.ui.userprofile.self.model.OtherAccount
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.kalium.logic.data.user.UserAvailabilityStatus
import com.wire.kalium.logic.data.user.UserId
+import kotlinx.collections.immutable.toImmutableList
val LocalSelfUserProfileLogoutAction = staticCompositionLocalOf<((wipeData: Boolean) -> Unit)?> {
null
@@ -437,7 +438,7 @@ private fun CurrentSelfUserStatus(
UserAvailabilityStatus.AWAY -> stringResource(UICommonR.string.user_profile_status_away)
UserAvailabilityStatus.NONE -> stringResource(UICommonR.string.user_profile_status_none)
}
- },
+ }.toImmutableList(),
defaultItemIndex = items.indexOf(userStatus),
label = null,
modifier = Modifier.padding(horizontal = MaterialTheme.wireDimensions.spacing16x),
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index f057a65319..02bae93df5 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -206,8 +206,6 @@
Zurück zu Unterhaltungsdetails
Anfrage akzeptieren oder ignorieren
Hinweis
- Auswahlliste
- Dropdown schließen
Benachrichtigungseinstellungen öffnen
Ansicht neue Unterhaltung schließen
Zurück zur Ansicht neue Unterhaltung
@@ -1129,9 +1127,6 @@
Team-Einstellungen geändert
Das Teilen und Empfangen von Dateien jeder Art ist jetzt aktiviert
Das Teilen und Empfangen von Dateien jeder Art ist jetzt deaktiviert
-
- " (Standard)"
- Bitte auswählen
Ändern
Gäste zulassen
Öffnen Sie diese Unterhaltung für Personen außerhalb Ihres Teams. Diese Einstellung kann später jederzeit geändert werden.
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 1a8ecb45af..3b505edc49 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -685,9 +685,6 @@ Un mensaje eliminado no puede ser restaurado.
Configuración del equipo cambiada
Ahora está habilitada la compartición y recepción de todo tipo de archivos
Ahora está deshabilitada la compartición y recepción de todo tipo de archivos
-
- " (predeterminado)"
- Seleccionar una opción
Cambiar
Permitir invitados
Abra esta conversación a personas fuera de su equipo. Siempre puede cambiarlo más tarde.
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 5416cbf0a4..52f64e06dc 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -459,8 +459,6 @@
Preuzmite datoteku
-
- Izaberi stavku
Promijeni
Dopustite goste
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 335a628a93..fe307e3844 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -180,10 +180,7 @@
Vissza a beszélgetés részleteihez
Vissza a beszélgetés részleteihez
a kérelem elfogadása vagy elutasítása
- megváltoztatás
Riasztás
- Lenyitás
- lenyíló elem összecsukása
értesítési beállítások megnyitása
Az új beszélgetés lap bezárása
Vissza az új beszélgetés lapra
@@ -968,9 +965,6 @@
A csapatbeállítások módosultak
Bármilyen típusú fájl megosztása és fogadása mostantól engedélyezve
Bármilyen típusú fájl megosztása és fogadása mostantól tiltva
-
- " (alapértelmezett)"
- Jelöljön ki egy elemet
Módosítás
Vendégek engedélyezése
Megnyitja a beszélgetést a csapatán kívüli személyek számára is. Ezt később bármikor megváltoztathatja.
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index bb4981423b..01fa47e8bc 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -717,9 +717,6 @@ Rispondendo qui, verrà riagganciata l\'altra chiamata.
Impostazioni del team modificate
La condivisione e la ricezione di file di qualsiasi tipo sono ora abilitate
La condivisione e la ricezione di file di qualsiasi tipo sono ora disabilitate
-
- " (predefinito)"
- Seleziona un elemento
Cambia
Consenti ospiti
Apri questa conversazione a persone esterne al tuo team. Puoi sempre cambiarla successivamente.
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index f73642b424..3dd130787d 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -612,9 +612,6 @@ Dołączenie do tego połączenia spowoduje zakończenie tam
Ustawienia zespołu zmienione
Udostępnianie i odbieranie plików dowolnego typu są teraz włączone
Udostępnianie i odbieranie plików dowolnego typu są teraz wyłączone
-
- " (domyślnie)"
- Wybierz
Zmień
Zezwól na gości
Otwórz tę rozmowę dla osób spoza twojego zespołu. Zawsze możesz to zmienić później.
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index eeac134d76..20e7b98438 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -178,10 +178,7 @@
Voltar para os detalhes da conversa
Voltar para os detalhes da conversa
aceitar ou ignorar a solicitação
- alterar isto
Alerta
- Menu suspenso
- fechar menu suspenso
abrir configurações de notificação
Fechar visualização de novas conversas
Voltar à visualização de nova conversa
@@ -916,9 +913,6 @@ Uma mensagem excluída não pode ser restaurada.
Configurações do time mudaram
Compartilhar e receber arquivos de todos os tipos está habilitado
Compartilhar e receber arquivos de todos os tipos está desabilitado
-
- " (padrão)"
- Selecione um item
Trocar
Permitir convidados
Abrir esta conversa para pessoas fora do seu time. Você pode sempre alterar isso depois.
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 45af6d0e8d..7e072b6217 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -213,10 +213,7 @@
Вернуться к сведениям о беседе
Вернуться к сведениям о беседе
принять или игнорировать запрос
- изменить
Оповещение
- Раскрывающийся список
- закрыть раскрывающийся список
открыть настройки уведомлений
Закрыть просмотр новой беседы
Вернуться к просмотру новой беседы
@@ -1189,9 +1186,6 @@
Настройки команды изменены
Обмен и получение файлов любого типа теперь включены
Обмен и получение файлов любого типа теперь отключены
-
- " (по умолчанию)"
- Выбрать элемент
Изменить
Разрешить гостей
Открыть эту беседу для пользователей не из вашей команды. Вы всегда сможете изменить это позже.
diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml
index 528317fa26..9ff757cded 100644
--- a/app/src/main/res/values-si/strings.xml
+++ b/app/src/main/res/values-si/strings.xml
@@ -180,10 +180,7 @@
සංවාද විස්තර වෙත ආපසු යන්න
සංවාද විස්තර වෙත ආපසු යන්න
ඉල්ලීම පිළිගන්න හෝ නොසලකා හරින්න
- එය වෙනස් කරන්න
අනතුරු ඇඟවීම
- පතන
- පතන ලැයිස්තුව වසන්න
දැනුම්දීම් සැකසුම් විවෘත කරන්න
නව සංවාද දසුන වසන්න
නව සංවාද දසුනට ආපසු යන්න
@@ -982,9 +979,6 @@
කණ්ඩායමේ සැකසුම් සංශෝධිතයි
ඕනෑම වර්ගයක ගොනු බෙදාගැනීම සහ ලැබීම සබල කර ඇත
ඕනෑම වර්ගයක ගොනු බෙදාගැනීම සහ ලැබීම අබල කර ඇත
-
- " (පෙරනිමි)"
- අථකයක් තෝරන්න
සංශෝධනය
අමුත්තන්ට ඉඩදෙන්න
ඔබගේ කණ්ඩායමෙන් පිටත පුද්ගලයින්ට මෙම සංවාදය විවෘත කරන්න. මෙය පසුව වෙනස් කිරීමට හැකිය.
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index dd05d13587..dc39eda50f 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -180,10 +180,7 @@
Konuşma detaylarına geri dön
Konuşma detaylarına geri dön
isteği kabul et veya yok say
- değiştir
Uyarı
- Açılır liste
- açılır listeyi kapat
bildirim ayarlarını aç
Yeni konuşma görünümünü kapat
Yeni konuşma görünümüne geri dön
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 4fe6a1bd66..260d1c82dd 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -154,9 +154,6 @@
Отримання та надсилання файлів будь-якого типу було увімкнено
Отримання та надсилання файлів будь-якого типу було вимкнено
-
- " (за замовчуванням)"
- Виберіть елемент
Змінити
Дозволити гостям
Відкрийте цю бесіду людям, які не належать до вашої команди. Ви завжди можете змінити це пізніше.
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b097bb865b..ac82a57b66 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -221,10 +221,7 @@
Go back to conversation details
Go back to conversation details
accept or ignore the request
- change it
Alert
- Dropdown
- close dropdown
open notification settings
Close new conversation view
Go back to new conversation view
@@ -1194,9 +1191,6 @@
Team Settings Changed
Sharing and receiving files of any type is now enabled
Sharing and receiving files of any type is now disabled
-
- " (default)"
- Select an Item
Change
Allow guests
Open this conversation to people outside your team. You can always change it later.
diff --git a/app/stability/app-devDebug.stability b/app/stability/app-devDebug.stability
index 79a3a1cf72..6e0ef40ecd 100644
--- a/app/stability/app-devDebug.stability
+++ b/app/stability/app-devDebug.stability
@@ -642,6 +642,14 @@ public fun com.wire.android.ui.CallFeedbackDialog(sheetState: com.wire.android.u
- onSkipClicked: STABLE (function type)
- modifier: STABLE (marked @Stable or @Immutable)
+@Composable
+public fun com.wire.android.ui.CrossBackendLoginBlockedDialog(shouldShow: kotlin.Boolean, onDismiss: kotlin.Function0): kotlin.Unit
+ skippable: true
+ restartable: true
+ params:
+ - shouldShow: STABLE (primitive type)
+ - onDismiss: STABLE (function type)
+
@Composable
public fun com.wire.android.ui.CustomBackendDialog(state: com.wire.android.ui.common.dialogs.CustomServerDialogState?, onDismiss: kotlin.Function0, onConfirm: kotlin.Function1, onTryAgain: kotlin.Function2): kotlin.Unit
skippable: true
@@ -1303,7 +1311,7 @@ public fun com.wire.android.ui.authentication.devices.remove.RemoveDeviceVerific
- viewModel: UNSTABLE (has mutable properties or unstable members)
@Composable
-private fun com.wire.android.ui.authentication.login.LoginContent(onBackPressed: kotlin.Function0, onSuccess: kotlin.Function3<@[ParameterName(name = \, onRemoveDeviceNeeded: kotlin.Function1, loginNavArgs: com.wire.android.ui.authentication.login.LoginNavArgs, loginEmailViewModel: com.wire.android.ui.authentication.login.email.LoginEmailViewModel, ssoLoginResult: com.wire.android.util.deeplink.DeepLinkResult.SSOLogin?, ssoCodeAutoLogin: com.wire.android.ui.authentication.login.SSOCodeAutoLogin?): kotlin.Unit
+private fun com.wire.android.ui.authentication.login.LoginContent(onBackPressed: kotlin.Function0, onSuccess: kotlin.Function3<@[ParameterName(name = \, onRemoveDeviceNeeded: kotlin.Function1, loginNavArgs: com.wire.android.ui.authentication.login.LoginNavArgs, loginEmailViewModel: com.wire.android.ui.authentication.login.email.LoginEmailViewModel, ssoLoginResult: com.wire.android.util.deeplink.DeepLinkResult.SSOLogin?, ssoCodeAutoLogin: com.wire.android.ui.authentication.login.SSOCodeAutoLogin?): kotlin.Unit
skippable: false
restartable: true
params:
@@ -1333,7 +1341,7 @@ public fun com.wire.android.ui.authentication.login.LoginScreen(navigator: com.w
- loginEmailViewModel: UNSTABLE (has mutable properties or unstable members)
@Composable
-private fun com.wire.android.ui.authentication.login.MainLoginContent(onBackPressed: kotlin.Function0, onSuccess: kotlin.Function3<@[ParameterName(name = \, onRemoveDeviceNeeded: kotlin.Function1, loginNavArgs: com.wire.android.ui.authentication.login.LoginNavArgs, loginEmailViewModel: com.wire.android.ui.authentication.login.email.LoginEmailViewModel, ssoLoginResult: com.wire.android.util.deeplink.DeepLinkResult.SSOLogin?, ssoCodeAutoLogin: com.wire.android.ui.authentication.login.SSOCodeAutoLogin?): kotlin.Unit
+private fun com.wire.android.ui.authentication.login.MainLoginContent(onBackPressed: kotlin.Function0, onSuccess: kotlin.Function3<@[ParameterName(name = \, onRemoveDeviceNeeded: kotlin.Function1, loginNavArgs: com.wire.android.ui.authentication.login.LoginNavArgs, loginEmailViewModel: com.wire.android.ui.authentication.login.email.LoginEmailViewModel, ssoLoginResult: com.wire.android.util.deeplink.DeepLinkResult.SSOLogin?, ssoCodeAutoLogin: com.wire.android.ui.authentication.login.SSOCodeAutoLogin?): kotlin.Unit
skippable: false
restartable: true
params:
@@ -1393,7 +1401,7 @@ private fun com.wire.android.ui.authentication.login.email.LoginEmailContent(scr
- fillMaxHeight: STABLE (primitive type)
@Composable
-public fun com.wire.android.ui.authentication.login.email.LoginEmailScreen(onSuccess: kotlin.Function3<@[ParameterName(name = \, onRemoveDeviceNeeded: kotlin.Function1, loginEmailViewModel: com.wire.android.ui.authentication.login.email.LoginEmailViewModel, scrollState: androidx.compose.foundation.ScrollState, fillMaxHeight: kotlin.Boolean): kotlin.Unit
+public fun com.wire.android.ui.authentication.login.email.LoginEmailScreen(onSuccess: kotlin.Function3<@[ParameterName(name = \, onRemoveDeviceNeeded: kotlin.Function1, loginEmailViewModel: com.wire.android.ui.authentication.login.email.LoginEmailViewModel, scrollState: androidx.compose.foundation.ScrollState, fillMaxHeight: kotlin.Boolean): kotlin.Unit
skippable: false
restartable: true
params:
@@ -1403,6 +1411,17 @@ public fun com.wire.android.ui.authentication.login.email.LoginEmailScreen(onSuc
- scrollState: STABLE (marked @Stable or @Immutable)
- fillMaxHeight: STABLE (primitive type)
+@Composable
+private fun com.wire.android.ui.authentication.login.email.LoginEmailStateNavigationAndDialogs(state: com.wire.android.ui.authentication.login.LoginState, domainClaimedByOrg: com.wire.android.ui.authentication.login.DomainClaimedByOrg?, onClearLoginErrors: kotlin.Function0, onSuccess: kotlin.Function3<@[ParameterName(name = \, onRemoveDeviceNeeded: kotlin.Function1): kotlin.Unit
+ skippable: true
+ restartable: true
+ params:
+ - state: STABLE (class with no mutable properties)
+ - domainClaimedByOrg: STABLE (class with no mutable properties)
+ - onClearLoginErrors: STABLE (function type)
+ - onSuccess: STABLE (function type)
+ - onRemoveDeviceNeeded: STABLE (function type)
+
@Composable
public fun com.wire.android.ui.authentication.login.email.LoginEmailVerificationCodeScreen(viewModel: com.wire.android.ui.authentication.login.email.LoginEmailViewModel): kotlin.Unit
skippable: false
@@ -1474,7 +1493,7 @@ private fun com.wire.android.ui.authentication.login.sso.LoginButton(loading: ko
- modifier: STABLE (marked @Stable or @Immutable)
@Composable
-private fun com.wire.android.ui.authentication.login.sso.LoginSSOContent(scrollState: androidx.compose.foundation.ScrollState, loginSSOState: com.wire.android.ui.authentication.login.sso.LoginSSOState, ssoCodeTextState: androidx.compose.foundation.text.input.TextFieldState, onErrorDialogDismiss: kotlin.Function0, onRemoveDeviceOpen: kotlin.Function1, onLoginButtonClick: kotlin.Function0, onCustomServerDialogDismiss: kotlin.Function0, onCustomServerDialogConfirm: kotlin.Function0): kotlin.Unit
+private fun com.wire.android.ui.authentication.login.sso.LoginSSOContent(scrollState: androidx.compose.foundation.ScrollState, loginSSOState: com.wire.android.ui.authentication.login.sso.LoginSSOState, ssoCodeTextState: androidx.compose.foundation.text.input.TextFieldState, onErrorDialogDismiss: kotlin.Function0, onRemoveDeviceOpen: kotlin.Function1, onLoginButtonClick: kotlin.Function0, onCustomServerDialogDismiss: kotlin.Function0, onCustomServerDialogConfirm: kotlin.Function0): kotlin.Unit
skippable: false
restartable: true
params:
@@ -1488,7 +1507,7 @@ private fun com.wire.android.ui.authentication.login.sso.LoginSSOContent(scrollS
- onCustomServerDialogConfirm: STABLE (function type)
@Composable
-public fun com.wire.android.ui.authentication.login.sso.LoginSSOScreen(onSuccess: kotlin.Function3<@[ParameterName(name = \, onRemoveDeviceNeeded: kotlin.Function1, loginNavArgs: com.wire.android.ui.authentication.login.LoginNavArgs, ssoLoginResult: com.wire.android.util.deeplink.DeepLinkResult.SSOLogin?, ssoCodeAutoLogin: com.wire.android.ui.authentication.login.SSOCodeAutoLogin?, loginSSOViewModel: com.wire.android.ui.authentication.login.sso.LoginSSOViewModel, scrollState: androidx.compose.foundation.ScrollState): kotlin.Unit
+public fun com.wire.android.ui.authentication.login.sso.LoginSSOScreen(onSuccess: kotlin.Function3<@[ParameterName(name = \, onRemoveDeviceNeeded: kotlin.Function1, loginNavArgs: com.wire.android.ui.authentication.login.LoginNavArgs, ssoLoginResult: com.wire.android.util.deeplink.DeepLinkResult.SSOLogin?, ssoCodeAutoLogin: com.wire.android.ui.authentication.login.SSOCodeAutoLogin?, loginSSOViewModel: com.wire.android.ui.authentication.login.sso.LoginSSOViewModel, scrollState: androidx.compose.foundation.ScrollState): kotlin.Unit
skippable: false
restartable: true
params:
@@ -2659,23 +2678,6 @@ public fun com.wire.android.ui.common.CopyButton(onCopyClicked: kotlin.Function0
- contentDescription: STABLE (primitive type)
- state: STABLE (class with no mutable properties)
-@Composable
-private fun com.wire.android.ui.common.DropdownItem(text: kotlin.String, leadingCompose: @[Composable] androidx.compose.runtime.internal.ComposableFunction0?, isSelected: kotlin.Boolean, onClick: kotlin.Function0): kotlin.Unit
- skippable: true
- restartable: true
- params:
- - text: STABLE (String is immutable)
- - leadingCompose: STABLE (composable function type)
- - isSelected: STABLE (primitive type)
- - onClick: STABLE (function type)
-
-@Composable
-private fun com.wire.android.ui.common.LeadingIcon(convent: @[Composable] androidx.compose.runtime.internal.ComposableFunction0): kotlin.Unit
- skippable: true
- restartable: true
- params:
- - convent: STABLE (composable function type)
-
@Composable
public fun com.wire.android.ui.common.LegalHoldIndicator(modifier: androidx.compose.ui.Modifier): kotlin.Unit
skippable: true
@@ -2721,25 +2723,6 @@ public fun com.wire.android.ui.common.MLSVerifiedIcon(modifier: androidx.compose
- modifier: STABLE (marked @Stable or @Immutable)
- contentDescriptionId: STABLE (primitive type)
-@Composable
-private fun com.wire.android.ui.common.MenuPopUp(shape: androidx.compose.foundation.shape.RoundedCornerShape, textFieldWidth: androidx.compose.ui.geometry.Size, expanded: kotlin.Boolean, borderColor: androidx.compose.ui.graphics.Color, leadingCompose: @[Composable] androidx.compose.runtime.internal.ComposableFunction1<@[ParameterName(name = \, selectedIndex: kotlin.Int, items: kotlin.collections.List, showDefaultTextIndicator: kotlin.Boolean, defaultItemIndex: kotlin.Int, selectionText: kotlin.String, arrowRotation: kotlin.Float, hidePopUp: kotlin.Function0, onChange: kotlin.Function1<@[ParameterName(name = \): kotlin.Unit
- skippable: false
- restartable: true
- params:
- - shape: STABLE (known stable type)
- - textFieldWidth: STABLE (marked @Stable or @Immutable)
- - expanded: STABLE (primitive type)
- - borderColor: STABLE (marked @Stable or @Immutable)
- - leadingCompose: STABLE (composable function type)
- - selectedIndex: STABLE (primitive type)
- - items: RUNTIME (requires runtime check)
- - showDefaultTextIndicator: STABLE (primitive type)
- - defaultItemIndex: STABLE (primitive type)
- - selectionText: STABLE (String is immutable)
- - arrowRotation: STABLE (primitive type)
- - hidePopUp: STABLE (function type)
- - onChange: STABLE (function type)
-
@Composable
public fun com.wire.android.ui.common.PageLoadingIndicator(text: kotlin.String, modifier: androidx.compose.ui.Modifier, prefixIconResId: kotlin.Int?): kotlin.Unit
skippable: true
@@ -2773,17 +2756,6 @@ public fun com.wire.android.ui.common.ProteusVerifiedIcon(modifier: androidx.com
- modifier: STABLE (marked @Stable or @Immutable)
- contentDescriptionId: STABLE (primitive type)
-@Composable
-private fun com.wire.android.ui.common.SelectionField(leadingCompose: @[Composable] androidx.compose.runtime.internal.ComposableFunction1<@[ParameterName(name = \, selectedIndex: kotlin.Int, text: kotlin.String, arrowRotation: kotlin.Float, modifier: androidx.compose.ui.Modifier): kotlin.Unit
- skippable: true
- restartable: true
- params:
- - leadingCompose: STABLE (composable function type)
- - selectedIndex: STABLE (primitive type)
- - text: STABLE (String is immutable)
- - arrowRotation: STABLE (primitive type)
- - modifier: STABLE (marked @Stable or @Immutable)
-
@Composable
public fun com.wire.android.ui.common.SettingUpWireScreenContent(modifier: androidx.compose.ui.Modifier, topBarTitleResId: kotlin.Int, iconResId: kotlin.Int, title: kotlin.String?, message: androidx.compose.ui.text.AnnotatedString, type: com.wire.android.ui.common.SettingUpWireScreenType): kotlin.Unit
skippable: true
@@ -2814,23 +2786,6 @@ public fun com.wire.android.ui.common.UnderConstructionScreen(screenName: kotlin
- screenName: STABLE (String is immutable)
- modifier: STABLE (marked @Stable or @Immutable)
-@Composable
-internal fun com.wire.android.ui.common.WireDropDown(items: kotlin.collections.List, label: kotlin.String?, modifier: androidx.compose.ui.Modifier, defaultItemIndex: kotlin.Int, selectedItemIndex: kotlin.Int, autoUpdateSelection: kotlin.Boolean, showDefaultTextIndicator: kotlin.Boolean, leadingCompose: @[Composable] androidx.compose.runtime.internal.ComposableFunction1<@[ParameterName(name = \, onChangeClickDescription: kotlin.String, placeholder: kotlin.String, onSelected: kotlin.Function1<@[ParameterName(name = \): kotlin.Unit
- skippable: false
- restartable: true
- params:
- - items: RUNTIME (requires runtime check)
- - label: STABLE (class with no mutable properties)
- - modifier: STABLE (marked @Stable or @Immutable)
- - defaultItemIndex: STABLE (primitive type)
- - selectedItemIndex: STABLE (primitive type)
- - autoUpdateSelection: STABLE (primitive type)
- - showDefaultTextIndicator: STABLE (primitive type)
- - leadingCompose: STABLE (composable function type)
- - onChangeClickDescription: STABLE (String is immutable)
- - placeholder: STABLE (String is immutable)
- - onSelected: STABLE (function type)
-
@Composable
public fun com.wire.android.ui.common.WireRadioButton(checked: kotlin.Boolean, modifier: androidx.compose.ui.Modifier, onButtonChecked: kotlin.Function0?, enabled: kotlin.Boolean): kotlin.Unit
skippable: true
@@ -6756,12 +6711,13 @@ private fun com.wire.android.ui.home.conversations.messages.item.buildContent(is
- isWireCellsEnabled: STABLE (primitive type)
@Composable
-private fun com.wire.android.ui.home.conversations.messages.item.buildContent(expandable: kotlin.Boolean, learnMoreLinkResId: kotlin.Int?, iconResId: kotlin.Int, iconTintColor: androidx.compose.ui.graphics.Color?, iconSize: androidx.compose.ui.unit.Dp, additionalVerticalPaddings: androidx.compose.ui.unit.Dp, backgroundColor: androidx.compose.ui.graphics.Color?, annotatedStringBuilder: @[Composable] androidx.compose.runtime.internal.ComposableFunction1<@[ParameterName(name = \): com.wire.android.ui.home.conversations.messages.item.SystemMessageContent
+private fun com.wire.android.ui.home.conversations.messages.item.buildContent(expandable: kotlin.Boolean, learnMoreLinkResId: kotlin.Int?, learnMoreTextResId: kotlin.Int, iconResId: kotlin.Int, iconTintColor: androidx.compose.ui.graphics.Color?, iconSize: androidx.compose.ui.unit.Dp, additionalVerticalPaddings: androidx.compose.ui.unit.Dp, backgroundColor: androidx.compose.ui.graphics.Color?, annotatedStringBuilder: @[Composable] androidx.compose.runtime.internal.ComposableFunction1<@[ParameterName(name = \): com.wire.android.ui.home.conversations.messages.item.SystemMessageContent
skippable: true
restartable: true
params:
- expandable: STABLE (primitive type)
- learnMoreLinkResId: STABLE (class with no mutable properties)
+ - learnMoreTextResId: STABLE (primitive type)
- iconResId: STABLE (primitive type)
- iconTintColor: STABLE (marked @Stable or @Immutable)
- iconSize: STABLE (marked @Stable or @Immutable)
@@ -11928,9 +11884,3 @@ public fun com.wire.android.util.ui.WireScrollableThemePreview(content: @[Compos
params:
- content: STABLE (composable function type)
-@Composable
-private fun com.wire.android.util.ui.createAnnotatedString(data: kotlin.collections.List): androidx.compose.ui.text.AnnotatedString
- skippable: false
- restartable: true
- params:
- - data: RUNTIME (requires runtime check)
diff --git a/core/ui-common/build.gradle.kts b/core/ui-common/build.gradle.kts
index dc62658bde..0379c07f79 100644
--- a/core/ui-common/build.gradle.kts
+++ b/core/ui-common/build.gradle.kts
@@ -29,6 +29,7 @@ dependencies {
implementation(libs.ktx.serialization)
implementation(libs.bundlizer.core)
implementation(libs.coroutines.android)
+ implementation(libs.ktx.immutableCollections)
val composeBom = platform(libs.compose.bom)
implementation(composeBom)
diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WireCheckIcon.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WireCheckIcon.kt
index 06f235e1d7..1e643daf58 100644
--- a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WireCheckIcon.kt
+++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WireCheckIcon.kt
@@ -32,7 +32,7 @@ import com.wire.android.ui.theme.wireDimensions
@Composable
fun WireCheckIcon(modifier: Modifier = Modifier, @StringRes contentDescription: Int = R.string.content_description_check) {
Icon(
- painter = painterResource(id = R.drawable.ic_check_circle),
+ painter = painterResource(id = R.drawable.ic_check_circle_filled),
contentDescription = stringResource(contentDescription),
modifier = modifier.size(MaterialTheme.wireDimensions.wireIconButtonSize),
tint = MaterialTheme.wireColorScheme.positive
diff --git a/app/src/main/kotlin/com/wire/android/ui/common/WireDropDown.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WireDropDown.kt
similarity index 87%
rename from app/src/main/kotlin/com/wire/android/ui/common/WireDropDown.kt
rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/WireDropDown.kt
index df85986a12..ceaad83de8 100644
--- a/app/src/main/kotlin/com/wire/android/ui/common/WireDropDown.kt
+++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WireDropDown.kt
@@ -1,6 +1,6 @@
/*
* Wire
- * Copyright (C) 2024 Wire Swiss GmbH
+ * Copyright (C) 2026 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
@@ -62,8 +63,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
-import com.wire.android.R
-import com.wire.android.ui.common.R as commonR
import com.wire.android.ui.common.textfield.WireLabel
import com.wire.android.ui.common.textfield.WireTextFieldState
import com.wire.android.ui.theme.wireColorScheme
@@ -71,16 +70,19 @@ import com.wire.android.ui.theme.wireDimensions
import com.wire.android.ui.theme.wireTypography
import com.wire.android.util.EMPTY
import com.wire.kalium.logic.data.conversation.CreateConversationParam
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.toImmutableList
@Composable
-internal fun WireDropDown(
- items: List,
+fun WireDropDown(
+ items: ImmutableList,
label: String?,
modifier: Modifier = Modifier,
defaultItemIndex: Int = -1,
selectedItemIndex: Int = defaultItemIndex,
autoUpdateSelection: Boolean = true,
showDefaultTextIndicator: Boolean = true,
+ showSelectionFieldWhenExpanded: Boolean = true,
leadingCompose: @Composable ((index: Int) -> Unit)? = null,
onChangeClickDescription: String = stringResource(R.string.content_description_change_it_label),
placeholder: String = stringResource(R.string.wire_dropdown_placeholder),
@@ -88,7 +90,7 @@ internal fun WireDropDown(
) {
var expanded by remember { mutableStateOf(false) }
var selectedIndex by remember(selectedItemIndex) { mutableStateOf(selectedItemIndex) }
- var selectionFieldWidth by remember { mutableStateOf(Size.Zero) }
+ var selectionFieldSize by remember { mutableStateOf(Size.Zero) }
val arrowRotation: Float by animateFloatAsState(if (expanded) 180f else 0f)
val selectionText = if (selectedIndex != -1) {
items[selectedIndex] + LocalContext.current.defaultTextIndicator(
@@ -124,7 +126,7 @@ internal fun WireDropDown(
.onGloballyPositioned { coordinates ->
// This value is used to assign to
// the DropDown the same width
- selectionFieldWidth = coordinates.size.toSize()
+ selectionFieldSize = coordinates.size.toSize()
}
.clickable(onClickLabel = onChangeClickDescription) { expanded = true },
leadingCompose = leadingCompose,
@@ -135,13 +137,14 @@ internal fun WireDropDown(
MenuPopUp(
shape = shape,
- textFieldWidth = selectionFieldWidth,
+ textFieldSize = selectionFieldSize,
expanded = expanded,
borderColor = borderColor,
leadingCompose = leadingCompose,
selectedIndex = selectedIndex,
items = items,
showDefaultTextIndicator = showDefaultTextIndicator,
+ showSelectionField = showSelectionFieldWhenExpanded,
defaultItemIndex = defaultItemIndex,
selectionText = selectionText,
arrowRotation = arrowRotation,
@@ -159,13 +162,14 @@ internal fun WireDropDown(
@Composable
private fun MenuPopUp(
shape: RoundedCornerShape,
- textFieldWidth: Size,
+ textFieldSize: Size,
expanded: Boolean,
borderColor: Color,
leadingCompose: @Composable ((index: Int) -> Unit)?,
selectedIndex: Int,
- items: List,
+ items: ImmutableList,
showDefaultTextIndicator: Boolean,
+ showSelectionField: Boolean,
defaultItemIndex: Int,
selectionText: String,
arrowRotation: Float,
@@ -178,28 +182,30 @@ private fun MenuPopUp(
// we want PopUp to cover the selection field, so we set this offset.
// "- 8.dp" is because DropdownMenu has inner top padding, which can't be changed,
// so without this additional 8.dp selection text will "jump" while opening/closing menu.
- val popUpTopOffset = with(LocalDensity.current) { -textFieldWidth.height.toDp() - 8.dp }
+ val popUpTopOffset = with(LocalDensity.current) { -textFieldSize.height.toDp() - 8.dp }
DropdownMenu(
expanded = expanded,
onDismissRequest = hidePopUp,
offset = DpOffset(0.dp, popUpTopOffset),
modifier = Modifier
- .width(with(LocalDensity.current) { textFieldWidth.width.toDp() })
+ .width(with(LocalDensity.current) { textFieldSize.width.toDp() })
.background(color = MaterialTheme.wireColorScheme.secondaryButtonEnabled)
.border(width = 1.dp, color = borderColor, shape)
.semantics { paneTitle = dropdownDescription }
) {
- SelectionField(
- leadingCompose = leadingCompose,
- selectedIndex = selectedIndex,
- text = selectionText,
- arrowRotation = arrowRotation,
- modifier = Modifier.clickable(onClickLabel = stringResource(R.string.content_description_close_dropdown)) {
- hidePopUp()
- }
- )
+ if (showSelectionField) {
+ SelectionField(
+ leadingCompose = leadingCompose,
+ selectedIndex = selectedIndex,
+ text = selectionText,
+ arrowRotation = arrowRotation,
+ modifier = Modifier.clickable(onClickLabel = stringResource(R.string.content_description_close_dropdown)) {
+ hidePopUp()
+ }
+ )
+ }
List(items.size) { index ->
HorizontalDivider(
@@ -232,12 +238,9 @@ private fun SelectionField(
Row(
modifier
.padding(
- start = dimensions().spacing16x,
- end = dimensions().spacing16x,
- top = dimensions().spacing12x,
- bottom = dimensions().spacing12x
+ horizontal = dimensions().spacing16x,
+ vertical = dimensions().spacing12x,
)
-
) {
leadingCompose?.let {
LeadingIcon { it(selectedIndex) }
@@ -255,7 +258,7 @@ private fun SelectionField(
}
)
Icon(
- painter = painterResource(commonR.drawable.ic_expand_more),
+ painter = painterResource(R.drawable.ic_arrow_drop_down),
contentDescription = null,
tint = MaterialTheme.wireColorScheme.secondaryText,
modifier = Modifier
@@ -277,7 +280,7 @@ private fun DropdownItem(
isSelected: Boolean,
onClick: () -> Unit
) {
- val selectLabel = stringResource(commonR.string.content_description_select_label)
+ val selectLabel = stringResource(R.string.content_description_select_label)
val closeDropdownLabel = stringResource(R.string.content_description_close_dropdown)
return DropdownMenuItem(
text = {
@@ -299,6 +302,10 @@ private fun DropdownItem(
}
},
onClick = onClick,
+ contentPadding = PaddingValues(
+ horizontal = dimensions().spacing16x,
+ vertical = dimensions().spacing12x,
+ ),
modifier = Modifier
.semantics {
if (isSelected) {
@@ -333,7 +340,7 @@ private fun RowScope.LeadingIcon(convent: @Composable () -> Unit) {
@Preview
fun PreviewWireDropdownPreviewWithLabel() {
WireDropDown(
- items = CreateConversationParam.Protocol.entries.map { it.name },
+ items = CreateConversationParam.Protocol.entries.map { it.name }.toImmutableList(),
defaultItemIndex = 0,
selectedItemIndex = 0,
label = "Protocol",
diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextField.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextField.kt
index 648da699a1..0011eeb3a8 100644
--- a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextField.kt
+++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextField.kt
@@ -95,6 +95,7 @@ fun WireTextField(
onTap: (() -> Unit)? = null,
testTag: String = String.EMPTY,
validateKeyboardOptions: Boolean = true,
+ readOnly: Boolean = false,
enabled: Boolean = state !is WireTextFieldState.Disabled,
) {
if (validateKeyboardOptions) {
@@ -150,7 +151,7 @@ fun WireTextField(
inputTransformation = inputTransformation,
outputTransformation = outputTransformation,
scrollState = scrollState,
- readOnly = state is WireTextFieldState.ReadOnly,
+ readOnly = readOnly,
enabled = enabled,
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
interactionSource = interactionSource,
diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextFieldLayout.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextFieldLayout.kt
index dd2b576f1a..092830d718 100644
--- a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextFieldLayout.kt
+++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextFieldLayout.kt
@@ -48,6 +48,7 @@ import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
@@ -204,6 +205,8 @@ private fun InnerTextLayout(
text = placeholderText,
style = placeholderTextStyle,
color = colors.placeholderColor(style).value,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
modifier = Modifier
.align(placeholderAlignment.toAlignment())
.clearAndSetSemantics {}
diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextFieldState.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextFieldState.kt
index fce9a95cd6..d5d97a81ff 100644
--- a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextFieldState.kt
+++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/textfield/WireTextFieldState.kt
@@ -31,7 +31,6 @@ sealed class WireTextFieldState {
data object Success : WireTextFieldState()
data object Disabled : WireTextFieldState()
- data object ReadOnly : WireTextFieldState()
fun icon(): Int? = when (this) {
is Error -> R.drawable.ic_error_outline
diff --git a/core/ui-common/src/main/res/drawable/ic_check_circle_filled.xml b/core/ui-common/src/main/res/drawable/ic_check_circle_filled.xml
new file mode 100644
index 0000000000..983b14ff4d
--- /dev/null
+++ b/core/ui-common/src/main/res/drawable/ic_check_circle_filled.xml
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/core/ui-common/src/main/res/values-de/strings.xml b/core/ui-common/src/main/res/values-de/strings.xml
index e2823d7ed6..59a2c81bf2 100644
--- a/core/ui-common/src/main/res/values-de/strings.xml
+++ b/core/ui-common/src/main/res/values-de/strings.xml
@@ -106,4 +106,9 @@
Abwählen
Mehr anzeigen
Weniger anzeigen
+ Bitte auswählen
+ Auswahlliste
+ Dropdown schließen
+
+ " (Standard)"
diff --git a/core/ui-common/src/main/res/values-es/strings.xml b/core/ui-common/src/main/res/values-es/strings.xml
index 292fa33700..d4b3eec86f 100644
--- a/core/ui-common/src/main/res/values-es/strings.xml
+++ b/core/ui-common/src/main/res/values-es/strings.xml
@@ -21,4 +21,7 @@
+ Seleccionar una opción
+
+ " (predeterminado)"
diff --git a/core/ui-common/src/main/res/values-hr/strings.xml b/core/ui-common/src/main/res/values-hr/strings.xml
index 292fa33700..da940aa44d 100644
--- a/core/ui-common/src/main/res/values-hr/strings.xml
+++ b/core/ui-common/src/main/res/values-hr/strings.xml
@@ -21,4 +21,6 @@
+
+ Izaberi stavku
diff --git a/core/ui-common/src/main/res/values-hu/strings.xml b/core/ui-common/src/main/res/values-hu/strings.xml
index 292fa33700..9dc7a5193c 100644
--- a/core/ui-common/src/main/res/values-hu/strings.xml
+++ b/core/ui-common/src/main/res/values-hu/strings.xml
@@ -21,4 +21,10 @@
+ megváltoztatás
+ Jelöljön ki egy elemet
+ Lenyitás
+ lenyíló elem összecsukása
+
+ " (alapértelmezett)"
diff --git a/core/ui-common/src/main/res/values-it/strings.xml b/core/ui-common/src/main/res/values-it/strings.xml
index 292fa33700..d172592811 100644
--- a/core/ui-common/src/main/res/values-it/strings.xml
+++ b/core/ui-common/src/main/res/values-it/strings.xml
@@ -21,4 +21,7 @@
+ Seleziona un elemento
+
+ " (predefinito)"
diff --git a/core/ui-common/src/main/res/values-pl/strings.xml b/core/ui-common/src/main/res/values-pl/strings.xml
index 292fa33700..38baa9f69c 100644
--- a/core/ui-common/src/main/res/values-pl/strings.xml
+++ b/core/ui-common/src/main/res/values-pl/strings.xml
@@ -21,4 +21,7 @@
+ Wybierz
+
+ " (domyślnie)"
diff --git a/core/ui-common/src/main/res/values-pt/strings.xml b/core/ui-common/src/main/res/values-pt/strings.xml
index 292fa33700..40ca0d4901 100644
--- a/core/ui-common/src/main/res/values-pt/strings.xml
+++ b/core/ui-common/src/main/res/values-pt/strings.xml
@@ -21,4 +21,10 @@
+ alterar isto
+ Selecione um item
+ Menu suspenso
+ fechar menu suspenso
+
+ " (padrão)"
diff --git a/core/ui-common/src/main/res/values-ru/strings.xml b/core/ui-common/src/main/res/values-ru/strings.xml
index bf6149e557..782d444f92 100644
--- a/core/ui-common/src/main/res/values-ru/strings.xml
+++ b/core/ui-common/src/main/res/values-ru/strings.xml
@@ -114,4 +114,10 @@
отменить выбор
Развернуть
Свернуть
+ изменить
+ Выбрать элемент
+ Раскрывающийся список
+ закрыть раскрывающийся список
+
+ " (по умолчанию)"
diff --git a/core/ui-common/src/main/res/values-si/strings.xml b/core/ui-common/src/main/res/values-si/strings.xml
index 292fa33700..1bf6d0ed70 100644
--- a/core/ui-common/src/main/res/values-si/strings.xml
+++ b/core/ui-common/src/main/res/values-si/strings.xml
@@ -21,4 +21,10 @@
+ එය වෙනස් කරන්න
+ අථකයක් තෝරන්න
+ පතන
+ පතන ලැයිස්තුව වසන්න
+
+ " (පෙරනිමි)"
diff --git a/core/ui-common/src/main/res/values-tr/strings.xml b/core/ui-common/src/main/res/values-tr/strings.xml
index 7171691c30..90d76fa527 100644
--- a/core/ui-common/src/main/res/values-tr/strings.xml
+++ b/core/ui-common/src/main/res/values-tr/strings.xml
@@ -33,4 +33,7 @@
+ değiştir
+ Açılır liste
+ açılır listeyi kapat
diff --git a/core/ui-common/src/main/res/values-uk/strings.xml b/core/ui-common/src/main/res/values-uk/strings.xml
index 292fa33700..d82393e5e1 100644
--- a/core/ui-common/src/main/res/values-uk/strings.xml
+++ b/core/ui-common/src/main/res/values-uk/strings.xml
@@ -21,4 +21,7 @@
+ Виберіть елемент
+
+ " (за замовчуванням)"
diff --git a/core/ui-common/src/main/res/values/strings.xml b/core/ui-common/src/main/res/values/strings.xml
index c6b30d5517..7cee9f5d84 100644
--- a/core/ui-common/src/main/res/values/strings.xml
+++ b/core/ui-common/src/main/res/values/strings.xml
@@ -124,4 +124,12 @@
Show More
Show Less
Select
+
+
+ change it
+ Select an Item
+ Dropdown
+ close dropdown
+
+ " (default)"
diff --git a/core/ui-common/stability/ui-common-debug.stability b/core/ui-common/stability/ui-common-debug.stability
index c4fab2d995..29c11c7f65 100644
--- a/core/ui-common/stability/ui-common-debug.stability
+++ b/core/ui-common/stability/ui-common-debug.stability
@@ -96,6 +96,16 @@ private fun com.wire.android.ui.common.DialogButtonsSection(dismissButtonPropert
- optionButton2Properties: STABLE (class with no mutable properties)
- buttonsHorizontalAlignment: STABLE (primitive type)
+@Composable
+private fun com.wire.android.ui.common.DropdownItem(text: kotlin.String, leadingCompose: @[Composable] androidx.compose.runtime.internal.ComposableFunction0?, isSelected: kotlin.Boolean, onClick: kotlin.Function0): kotlin.Unit
+ skippable: true
+ restartable: true
+ params:
+ - text: STABLE (String is immutable)
+ - leadingCompose: STABLE (composable function type)
+ - isSelected: STABLE (primitive type)
+ - onClick: STABLE (function type)
+
@Composable
public fun com.wire.android.ui.common.HandleActions(actionsFlow: kotlinx.coroutines.flow.Flow, onAction: kotlin.Function1): kotlin.Unit
skippable: false
@@ -111,6 +121,13 @@ public fun com.wire.android.ui.common.Icon(modifier: androidx.compose.ui.Modifie
params:
- modifier: STABLE (marked @Stable or @Immutable)
+@Composable
+private fun com.wire.android.ui.common.LeadingIcon(convent: @[Composable] androidx.compose.runtime.internal.ComposableFunction0): kotlin.Unit
+ skippable: true
+ restartable: true
+ params:
+ - convent: STABLE (composable function type)
+
@Composable
public fun com.wire.android.ui.common.LoadingWireTabRow(modifier: androidx.compose.ui.Modifier): kotlin.Unit
skippable: true
@@ -126,6 +143,26 @@ public fun com.wire.android.ui.common.MembershipQualifierLabel(membership: com.w
- membership: STABLE (marked @Stable or @Immutable)
- modifier: STABLE (marked @Stable or @Immutable)
+@Composable
+private fun com.wire.android.ui.common.MenuPopUp(shape: androidx.compose.foundation.shape.RoundedCornerShape, textFieldSize: androidx.compose.ui.geometry.Size, expanded: kotlin.Boolean, borderColor: androidx.compose.ui.graphics.Color, leadingCompose: @[Composable] androidx.compose.runtime.internal.ComposableFunction1<@[ParameterName(name = \, selectedIndex: kotlin.Int, items: kotlinx.collections.immutable.ImmutableList, showDefaultTextIndicator: kotlin.Boolean, showSelectionField: kotlin.Boolean, defaultItemIndex: kotlin.Int, selectionText: kotlin.String, arrowRotation: kotlin.Float, hidePopUp: kotlin.Function0, onChange: kotlin.Function1<@[ParameterName(name = \): kotlin.Unit
+ skippable: true
+ restartable: true
+ params:
+ - shape: STABLE (known stable type)
+ - textFieldSize: STABLE (marked @Stable or @Immutable)
+ - expanded: STABLE (primitive type)
+ - borderColor: STABLE (marked @Stable or @Immutable)
+ - leadingCompose: STABLE (composable function type)
+ - selectedIndex: STABLE (primitive type)
+ - items: STABLE (known stable type)
+ - showDefaultTextIndicator: STABLE (primitive type)
+ - showSelectionField: STABLE (primitive type)
+ - defaultItemIndex: STABLE (primitive type)
+ - selectionText: STABLE (String is immutable)
+ - arrowRotation: STABLE (primitive type)
+ - hidePopUp: STABLE (function type)
+ - onChange: STABLE (function type)
+
@Composable
public fun com.wire.android.ui.common.MoreOptionIcon(onButtonClicked: kotlin.Function0, modifier: androidx.compose.ui.Modifier, state: com.wire.android.ui.common.button.WireButtonState, contentDescription: kotlin.Int): kotlin.Unit
skippable: true
@@ -186,6 +223,17 @@ public fun com.wire.android.ui.common.SearchBarInput(placeholderText: kotlin.Str
- semanticDescription: STABLE (class with no mutable properties)
- onTap: STABLE (function type)
+@Composable
+private fun com.wire.android.ui.common.SelectionField(leadingCompose: @[Composable] androidx.compose.runtime.internal.ComposableFunction1<@[ParameterName(name = \, selectedIndex: kotlin.Int, text: kotlin.String, arrowRotation: kotlin.Float, modifier: androidx.compose.ui.Modifier): kotlin.Unit
+ skippable: true
+ restartable: true
+ params:
+ - leadingCompose: STABLE (composable function type)
+ - selectedIndex: STABLE (primitive type)
+ - text: STABLE (String is immutable)
+ - arrowRotation: STABLE (primitive type)
+ - modifier: STABLE (marked @Stable or @Immutable)
+
@Composable
public fun com.wire.android.ui.common.StatusBox(statusText: kotlin.String, modifier: androidx.compose.ui.Modifier, textColor: androidx.compose.ui.graphics.Color, badgeColor: androidx.compose.ui.graphics.Color, withBorder: kotlin.Boolean): kotlin.Unit
skippable: true
@@ -339,6 +387,24 @@ public fun com.wire.android.ui.common.WireDialogContent(title: kotlin.String?, m
- centerContent: STABLE (primitive type)
- content: STABLE (composable function type)
+@Composable
+public fun com.wire.android.ui.common.WireDropDown(items: kotlinx.collections.immutable.ImmutableList, label: kotlin.String?, modifier: androidx.compose.ui.Modifier, defaultItemIndex: kotlin.Int, selectedItemIndex: kotlin.Int, autoUpdateSelection: kotlin.Boolean, showDefaultTextIndicator: kotlin.Boolean, showSelectionFieldWhenExpanded: kotlin.Boolean, leadingCompose: @[Composable] androidx.compose.runtime.internal.ComposableFunction1<@[ParameterName(name = \, onChangeClickDescription: kotlin.String, placeholder: kotlin.String, onSelected: kotlin.Function1<@[ParameterName(name = \): kotlin.Unit
+ skippable: true
+ restartable: true
+ params:
+ - items: STABLE (known stable type)
+ - label: STABLE (class with no mutable properties)
+ - modifier: STABLE (marked @Stable or @Immutable)
+ - defaultItemIndex: STABLE (primitive type)
+ - selectedItemIndex: STABLE (primitive type)
+ - autoUpdateSelection: STABLE (primitive type)
+ - showDefaultTextIndicator: STABLE (primitive type)
+ - showSelectionFieldWhenExpanded: STABLE (primitive type)
+ - leadingCompose: STABLE (composable function type)
+ - onChangeClickDescription: STABLE (String is immutable)
+ - placeholder: STABLE (String is immutable)
+ - onSelected: STABLE (function type)
+
@Composable
private fun com.wire.android.ui.common.WireIndicator(modifier: androidx.compose.ui.Modifier): kotlin.Unit
skippable: true
@@ -1850,7 +1916,7 @@ public fun com.wire.android.ui.common.textfield.WirePasswordTextField(textState:
- testTag: STABLE (String is immutable)
@Composable
-public fun com.wire.android.ui.common.textfield.WireTextField(textState: androidx.compose.foundation.text.input.TextFieldState, modifier: androidx.compose.ui.Modifier, inputModifier: androidx.compose.ui.Modifier, placeholderText: kotlin.String?, labelText: kotlin.String?, labelMandatoryIcon: kotlin.Boolean, descriptionText: kotlin.String?, semanticDescription: kotlin.String?, leadingIcon: @[Composable] androidx.compose.runtime.internal.ComposableFunction0?, trailingIcon: @[Composable] androidx.compose.runtime.internal.ComposableFunction0?, state: com.wire.android.ui.common.textfield.WireTextFieldState, autoFillType: com.wire.android.ui.common.textfield.WireAutoFillType, lineLimits: androidx.compose.foundation.text.input.TextFieldLineLimits, inputTransformation: androidx.compose.foundation.text.input.InputTransformation, outputTransformation: androidx.compose.foundation.text.input.OutputTransformation?, keyboardOptions: androidx.compose.foundation.text.KeyboardOptions, onKeyboardAction: androidx.compose.foundation.text.input.KeyboardActionHandler?, scrollState: androidx.compose.foundation.ScrollState, interactionSource: androidx.compose.foundation.interaction.MutableInteractionSource, textStyle: androidx.compose.ui.text.TextStyle, placeholderTextStyle: androidx.compose.ui.text.TextStyle, placeholderAlignment: androidx.compose.ui.Alignment.Horizontal, inputMinHeight: androidx.compose.ui.unit.Dp, shape: androidx.compose.ui.graphics.Shape, colors: com.wire.android.ui.common.textfield.WireTextFieldColors, onSelectedLineIndexChanged: kotlin.Function1, onLineBottomYCoordinateChanged: kotlin.Function1, onInputSizeChanged: kotlin.Function1, onTap: kotlin.Function0?, testTag: kotlin.String, validateKeyboardOptions: kotlin.Boolean, enabled: kotlin.Boolean): kotlin.Unit
+public fun com.wire.android.ui.common.textfield.WireTextField(textState: androidx.compose.foundation.text.input.TextFieldState, modifier: androidx.compose.ui.Modifier, inputModifier: androidx.compose.ui.Modifier, placeholderText: kotlin.String?, labelText: kotlin.String?, labelMandatoryIcon: kotlin.Boolean, descriptionText: kotlin.String?, semanticDescription: kotlin.String?, leadingIcon: @[Composable] androidx.compose.runtime.internal.ComposableFunction0?, trailingIcon: @[Composable] androidx.compose.runtime.internal.ComposableFunction0?, state: com.wire.android.ui.common.textfield.WireTextFieldState, autoFillType: com.wire.android.ui.common.textfield.WireAutoFillType, lineLimits: androidx.compose.foundation.text.input.TextFieldLineLimits, inputTransformation: androidx.compose.foundation.text.input.InputTransformation, outputTransformation: androidx.compose.foundation.text.input.OutputTransformation?, keyboardOptions: androidx.compose.foundation.text.KeyboardOptions, onKeyboardAction: androidx.compose.foundation.text.input.KeyboardActionHandler?, scrollState: androidx.compose.foundation.ScrollState, interactionSource: androidx.compose.foundation.interaction.MutableInteractionSource, textStyle: androidx.compose.ui.text.TextStyle, placeholderTextStyle: androidx.compose.ui.text.TextStyle, placeholderAlignment: androidx.compose.ui.Alignment.Horizontal, inputMinHeight: androidx.compose.ui.unit.Dp, shape: androidx.compose.ui.graphics.Shape, colors: com.wire.android.ui.common.textfield.WireTextFieldColors, onSelectedLineIndexChanged: kotlin.Function1, onLineBottomYCoordinateChanged: kotlin.Function1, onInputSizeChanged: kotlin.Function1, onTap: kotlin.Function0?, testTag: kotlin.String, validateKeyboardOptions: kotlin.Boolean, readOnly: kotlin.Boolean, enabled: kotlin.Boolean): kotlin.Unit
skippable: true
restartable: true
params:
@@ -1885,6 +1951,7 @@ public fun com.wire.android.ui.common.textfield.WireTextField(textState: android
- onTap: STABLE (function type)
- testTag: STABLE (String is immutable)
- validateKeyboardOptions: STABLE (primitive type)
+ - readOnly: STABLE (primitive type)
- enabled: STABLE (primitive type)
@Composable
diff --git a/features/meetings/src/main/java/com/wire/android/feature/meetings/model/MeetingItem.kt b/features/meetings/src/main/java/com/wire/android/feature/meetings/model/MeetingItem.kt
index 1cac7e3e3c..2e7aaf2f96 100644
--- a/features/meetings/src/main/java/com/wire/android/feature/meetings/model/MeetingItem.kt
+++ b/features/meetings/src/main/java/com/wire/android/feature/meetings/model/MeetingItem.kt
@@ -19,6 +19,7 @@ package com.wire.android.feature.meetings.model
import android.os.Parcelable
import androidx.annotation.StringRes
+import androidx.compose.runtime.Stable
import com.wire.android.feature.meetings.R
import com.wire.android.model.UserAvatarData
import com.wire.kalium.logic.data.id.ConversationId
@@ -29,17 +30,20 @@ import kotlin.time.Duration
sealed interface MeetingListItem
+@Stable
data class MeetingItem(
val meetingId: String,
val conversationId: ConversationId,
val belongingType: BelongingType,
- val repeatingInterval: RepeatingInterval?, // null for one-time meetings
+ val repeatingInterval: RepeatingInterval,
val title: String,
val status: Status,
val selfRole: SelfRole,
) : MeetingListItem {
+ @Stable
@Parcelize
enum class RepeatingInterval(@StringRes val nameResId: Int) : Parcelable {
+ Never(R.string.meeting_repeating_never),
Daily(R.string.meeting_repeating_daily),
Weekly(R.string.meeting_repeating_weekly),
BiWeekly(R.string.meeting_repeating_biweekly),
@@ -47,6 +51,7 @@ data class MeetingItem(
Annually(R.string.meeting_repeating_annually)
}
+ @Stable
sealed interface BelongingType {
data class Group(val name: String) : BelongingType
data class Channel(val name: String, val isPrivateChannel: Boolean) : BelongingType
@@ -54,8 +59,10 @@ data class MeetingItem(
data class Groupless(val avatars: ImmutableList, val limit: Int = GROUPLESS_AVATARS_LIMIT) : BelongingType
}
+ @Stable
sealed interface Status {
val startTime: Instant
+
data class Scheduled(
override val startTime: Instant, // scheduled start time
val endTime: Instant, // scheduled end time
@@ -75,11 +82,13 @@ data class MeetingItem(
}
}
+ @Stable
data class OngoingCallStatus(
val currentCallStartedTime: Instant, // time when the current call started (there can be many calls one after another in a meeting)
val isSelfUserAttending: Boolean // is the current user attending the ongoing call
)
+ @Stable
enum class SelfRole { Admin, Member }
}
diff --git a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/MeetingsViewModelFactory.kt b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/MeetingsViewModelFactory.kt
index 6fb88d571c..74ce997b18 100644
--- a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/MeetingsViewModelFactory.kt
+++ b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/MeetingsViewModelFactory.kt
@@ -42,5 +42,8 @@ class MeetingsViewModelFactory @Inject constructor(
internal fun meetingOptionsMenuViewModel() = MeetingOptionsMenuViewModelImpl(getMeeting = getMeeting)
- internal fun newMeetingViewModel(savedStateHandle: SavedStateHandle) = NewMeetingViewModelImpl(savedStateHandle)
+ internal fun newMeetingViewModel(savedStateHandle: SavedStateHandle) = NewMeetingViewModelImpl(
+ savedStateHandle = savedStateHandle,
+ currentTimeProvider = currentTimeProvider
+ )
}
diff --git a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingScreen.kt b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingScreen.kt
index 51bafd687b..483df3f3e2 100644
--- a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingScreen.kt
+++ b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingScreen.kt
@@ -20,23 +20,29 @@ package com.wire.android.feature.meetings.ui.create
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.clearText
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -49,9 +55,15 @@ import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
import androidx.compose.ui.text.rememberTextMeasurer
+import androidx.compose.ui.text.style.TextAlign
import com.ramcosta.composedestinations.generated.meetings.destinations.NewMeetingParticipantsScreenDestination
import com.wire.android.feature.meetings.R
+import com.wire.android.feature.meetings.model.MeetingItem
import com.wire.android.feature.meetings.ui.create.NewMeetingViewModel.Companion.MEETING_NAME_MAX_COUNT
import com.wire.android.feature.meetings.ui.util.PreviewMultipleThemes
import com.wire.android.model.Contact
@@ -60,11 +72,18 @@ import com.wire.android.navigation.WireNavigator
import com.wire.android.navigation.annotation.features.meetings.WireNewMeetingDestination
import com.wire.android.navigation.style.PopUpNavigationAnimation
import com.wire.android.ui.common.HandleActions
+import com.wire.android.ui.common.VisibilityState
+import com.wire.android.ui.common.WireDropDown
import com.wire.android.ui.common.animation.ShakeAnimation
import com.wire.android.ui.common.button.WireButtonState
import com.wire.android.ui.common.button.WirePrimaryButton
import com.wire.android.ui.common.colorsScheme
+import com.wire.android.ui.common.datetime.FutureSelectableDates
+import com.wire.android.ui.common.datetime.WireDatePickerDialog
+import com.wire.android.ui.common.datetime.WireTimePickerDialog
+import com.wire.android.ui.common.datetime.asTimePickerResult
import com.wire.android.ui.common.dimensions
+import com.wire.android.ui.common.rememberTopBarElevationState
import com.wire.android.ui.common.scaffold.WireScaffold
import com.wire.android.ui.common.spacers.VerticalSpace
import com.wire.android.ui.common.textfield.DefaultEmailDone
@@ -75,13 +94,25 @@ import com.wire.android.ui.common.textfield.maxLengthWithCallback
import com.wire.android.ui.common.topappbar.NavigationIconType
import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar
import com.wire.android.ui.common.typography
+import com.wire.android.ui.common.visbility.rememberVisibilityState
import com.wire.android.ui.home.conversationslist.model.Membership
import com.wire.android.ui.theme.WireTheme
import com.wire.android.ui.theme.wireColorScheme
import com.wire.android.ui.theme.wireDimensions
+import com.wire.android.ui.theme.wireTypography
+import com.wire.android.util.CurrentTimeProvider
+import com.wire.android.util.DateAndTimeParsers
+import com.wire.android.util.EMPTY
import com.wire.kalium.logic.data.user.ConnectionState
import kotlinx.collections.immutable.ImmutableSet
+import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentSet
+import kotlinx.datetime.Instant
+import kotlinx.datetime.LocalDateTime
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.toInstant
+import kotlinx.datetime.toLocalDateTime
+import kotlin.time.Duration.Companion.hours
import com.wire.android.ui.common.R as commonR
@WireNewMeetingDestination(
@@ -103,9 +134,10 @@ fun NewMeetingScreen(
onParticipantsClicked = {
navigator.navigate(NavigationCommand(NewMeetingParticipantsScreenDestination))
},
- onCreateClicked = {
- newMeetingViewModel.createMeeting()
- }
+ onCreateClicked = newMeetingViewModel::createMeeting,
+ onStartTimeChanged = newMeetingViewModel::updateStartTime,
+ onEndTimeChanged = newMeetingViewModel::updateEndTime,
+ onRepeatingIntervalChanged = newMeetingViewModel::updateRepeatingInterval,
)
HandleActions(newMeetingViewModel.actions) { action ->
@@ -124,12 +156,16 @@ fun NewMeetingContent(
onBackPressed: () -> Unit = {},
onParticipantsClicked: () -> Unit = {},
onCreateClicked: () -> Unit = {},
+ onStartTimeChanged: (startTime: Instant) -> Unit = {},
+ onEndTimeChanged: (endTime: Instant) -> Unit = {},
+ onRepeatingIntervalChanged: (interval: MeetingItem.RepeatingInterval) -> Unit = {},
) {
+ val scrollState = rememberScrollState()
WireScaffold(
modifier = modifier,
topBar = {
WireCenterAlignedTopAppBar(
- elevation = dimensions().spacing0x,
+ elevation = scrollState.rememberTopBarElevationState().value,
title = stringResource(type.title),
onNavigationPressed = onBackPressed,
navigationIconType = NavigationIconType.Back(
@@ -142,16 +178,41 @@ fun NewMeetingContent(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(internalPadding)
+ .verticalScroll(scrollState)
.padding(
- top = dimensions().spacing24x,
- start = dimensions().spacing16x,
- end = dimensions().spacing16x,
+ vertical = dimensions().spacing24x,
+ horizontal = dimensions().spacing16x,
)
) {
TitleInput(
titleState = titleState,
titleError = state.titleError,
)
+ if (type == NewMeetingType.Schedule) {
+ VerticalSpace.x24()
+ TimeInput(
+ time = state.startTime,
+ timeError = state.startTimeError,
+ onTimeChanged = onStartTimeChanged,
+ label = stringResource(R.string.new_meeting_starts_input_label),
+ datePlaceholder = stringResource(R.string.new_meeting_start_date_input_placeholder),
+ timePlaceholder = stringResource(R.string.new_meeting_start_time_input_placeholder),
+ )
+ VerticalSpace.x8()
+ TimeInput(
+ time = state.endTime,
+ timeError = state.endTimeError,
+ onTimeChanged = onEndTimeChanged,
+ label = stringResource(R.string.new_meeting_ends_input_label),
+ datePlaceholder = stringResource(R.string.new_meeting_end_date_input_placeholder),
+ timePlaceholder = stringResource(R.string.new_meeting_end_time_input_placeholder),
+ )
+ VerticalSpace.x8()
+ RepeatingIntervalDropDown(
+ repeatingInterval = state.repeatingInterval,
+ onRepeatingIntervalChanged = onRepeatingIntervalChanged,
+ )
+ }
VerticalSpace.x24()
ParticipantsInput(
participants = state.confirmedContacts,
@@ -266,13 +327,14 @@ private fun ParticipantsInput(
textFieldState.setTextAndPlaceCursorAtEnd(participants.joinToString(", ") { it.name })
}
+ val semanticDescription = stringResource(R.string.new_meeting_participants_input_placeholder)
WireTextField(
textState = textFieldState,
placeholderText = stringResource(R.string.new_meeting_participants_input_placeholder),
labelText = stringResource(R.string.new_meeting_participants_input_label).uppercase(),
- semanticDescription = stringResource(R.string.new_meeting_participants_input_placeholder),
keyboardOptions = KeyboardOptions.DefaultEmailDone,
- state = WireTextFieldState.ReadOnly,
+ state = WireTextFieldState.Default,
+ readOnly = true,
onInputSizeChanged = { innerTextWidthPx = it.width },
outputTransformation = truncationTransformation,
onTap = onClick,
@@ -282,20 +344,202 @@ private fun ParticipantsInput(
contentDescription = null,
tint = colorsScheme().onSurfaceVariant,
modifier = Modifier
- .padding(dimensions().spacing16x)
+ .padding(start = dimensions().spacing4x, end = dimensions().spacing16x)
.size(dimensions().spacing16x)
)
+ },
+ inputModifier = Modifier.clearAndSetSemantics {
+ contentDescription = semanticDescription
+ role = Role.Button
}
)
}
+@Composable
+private fun TimeInput(
+ time: Instant,
+ timeError: NewMeetingState.TimeError?,
+ onTimeChanged: (Instant) -> Unit,
+ label: String,
+ datePlaceholder: String,
+ timePlaceholder: String,
+) {
+ val dateTextFieldState = rememberTextFieldState(DateAndTimeParsers.meetingDate(time))
+ val timeTextFieldState = rememberTextFieldState(DateAndTimeParsers.meetingTime(time))
+ val datePickerDialogState = rememberVisibilityState()
+ val timePickerDialogState = rememberVisibilityState()
+
+ LaunchedEffect(time) {
+ dateTextFieldState.setTextAndPlaceCursorAtEnd(DateAndTimeParsers.meetingDate(time))
+ timeTextFieldState.setTextAndPlaceCursorAtEnd(DateAndTimeParsers.meetingTime(time))
+ }
+
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(-dimensions().spacing1x) // Pulls elements together by 1dp
+ ) {
+ WireTextField(
+ textState = dateTextFieldState,
+ placeholderText = datePlaceholder,
+ labelText = label.uppercase(),
+ keyboardOptions = KeyboardOptions.DefaultEmailDone,
+ state = if (timeError != null) WireTextFieldState.Error() else WireTextFieldState.Default,
+ readOnly = true,
+ shape = RoundedCornerShape(
+ topStart = dimensions().textFieldCornerSize,
+ bottomStart = dimensions().textFieldCornerSize,
+ topEnd = dimensions().spacing0x,
+ bottomEnd = dimensions().spacing0x,
+ ),
+ onTap = {
+ datePickerDialogState.show(Unit)
+ },
+ trailingIcon = {
+ Icon(
+ painter = painterResource(commonR.drawable.ic_calendar),
+ contentDescription = null,
+ tint = colorsScheme().onSurfaceVariant,
+ modifier = Modifier
+ .padding(start = dimensions().spacing4x, end = dimensions().spacing16x)
+ .size(dimensions().spacing16x)
+ )
+ },
+ modifier = Modifier.weight(2f),
+ inputModifier = Modifier.clearAndSetSemantics {
+ contentDescription = datePlaceholder
+ role = Role.Button
+ }
+ )
+ WireTextField(
+ textState = timeTextFieldState,
+ labelText = String.EMPTY, // Time input doesn't have a label as the date input's label already describes the field
+ keyboardOptions = KeyboardOptions.DefaultEmailDone,
+ state = if (timeError != null) WireTextFieldState.Error() else WireTextFieldState.Default,
+ readOnly = true,
+ shape = RoundedCornerShape(
+ topStart = dimensions().spacing0x,
+ bottomStart = dimensions().spacing0x,
+ topEnd = dimensions().textFieldCornerSize,
+ bottomEnd = dimensions().textFieldCornerSize,
+ ),
+ onTap = {
+ timePickerDialogState.show(Unit)
+ },
+ trailingIcon = {
+ Icon(
+ painter = painterResource(commonR.drawable.ic_arrow_drop_down),
+ contentDescription = null,
+ tint = colorsScheme().onSurfaceVariant,
+ modifier = Modifier
+ .padding(start = dimensions().spacing4x, end = dimensions().spacing16x)
+ .size(dimensions().spacing16x)
+ )
+ },
+ modifier = Modifier.weight(1f),
+ inputModifier = Modifier.clearAndSetSemantics {
+ contentDescription = timePlaceholder
+ role = Role.Button
+ }
+ )
+ }
+ AnimatedVisibility(visible = timeError != null) {
+ Text(
+ text = when (timeError) {
+ is NewMeetingState.TimeError.StartTimeInPastError -> stringResource(R.string.new_meeting_start_in_past_error)
+ is NewMeetingState.TimeError.EndTimeInPastError -> stringResource(R.string.new_meeting_end_in_past_error)
+ is NewMeetingState.TimeError.EndTimeBeforeStartTimeError -> stringResource(R.string.new_meeting_end_before_start_error)
+ else -> String.EMPTY
+ },
+ style = MaterialTheme.wireTypography.label04,
+ textAlign = TextAlign.Start,
+ color = colorsScheme().error,
+ modifier = Modifier.padding(top = dimensions().spacing4x)
+ )
+ }
+ }
+ VisibilityState(status = datePickerDialogState) {
+ WireDatePickerDialog(
+ title = datePlaceholder,
+ selectedDateMillis = time.toEpochMilliseconds(),
+ selectableDates = FutureSelectableDates(),
+ onDateSelected = { millis ->
+ if (millis != null) {
+ val timeZone = TimeZone.currentSystemDefault()
+ val timeDateTime = time.toLocalDateTime(timeZone)
+ val dateDateTime = Instant.fromEpochMilliseconds(millis).toLocalDateTime(timeZone)
+ val combinedDateTime = LocalDateTime(
+ year = dateDateTime.year,
+ monthNumber = dateDateTime.monthNumber,
+ dayOfMonth = dateDateTime.dayOfMonth,
+ hour = timeDateTime.hour,
+ minute = timeDateTime.minute,
+ second = 0,
+ nanosecond = 0
+ )
+ onTimeChanged(combinedDateTime.toInstant(timeZone))
+ datePickerDialogState.dismiss()
+ }
+ },
+ onDismiss = datePickerDialogState::dismiss,
+ )
+ }
+ VisibilityState(status = timePickerDialogState) {
+ WireTimePickerDialog(
+ title = timePlaceholder,
+ selectedTime = time.toEpochMilliseconds().asTimePickerResult(),
+ onTimeSelected = { timePickerResult ->
+ val timeZone = TimeZone.currentSystemDefault()
+ val dateDateTime = time.toLocalDateTime(timeZone)
+ val combinedDateTime = LocalDateTime(
+ year = dateDateTime.year,
+ monthNumber = dateDateTime.monthNumber,
+ dayOfMonth = dateDateTime.dayOfMonth,
+ hour = timePickerResult.hour,
+ minute = timePickerResult.minute,
+ second = 0,
+ nanosecond = 0
+ )
+ onTimeChanged(combinedDateTime.toInstant(timeZone))
+ timePickerDialogState.dismiss()
+ },
+ onDismiss = timePickerDialogState::dismiss,
+ )
+ }
+}
+
+@Composable
+private fun RepeatingIntervalDropDown(
+ repeatingInterval: MeetingItem.RepeatingInterval,
+ onRepeatingIntervalChanged: (MeetingItem.RepeatingInterval) -> Unit,
+ items: List = MeetingItem.RepeatingInterval.entries,
+) {
+ val resources = LocalResources.current
+ WireDropDown(
+ items = remember(resources) {
+ items.map { resources.getString(it.nameResId) }.toImmutableList()
+ },
+ defaultItemIndex = items.indexOf(repeatingInterval),
+ label = stringResource(R.string.new_meeting_repeats_input_label).uppercase(),
+ autoUpdateSelection = false,
+ showDefaultTextIndicator = false,
+ showSelectionFieldWhenExpanded = false,
+ onChangeClickDescription = stringResource(R.string.content_description_new_meeting_repeating_options)
+ ) { selectedIndex ->
+ onRepeatingIntervalChanged(items[selectedIndex])
+ }
+}
+
@PreviewMultipleThemes
@Composable
fun PreviewNewMeetingScreen_MeetNow() = WireTheme {
NewMeetingContent(
titleState = rememberTextFieldState("Meeting with 9 users"),
type = NewMeetingType.MeetNow,
- state = NewMeetingState(confirmedContacts = buildContacts(names.size), continueButtonEnabled = true),
+ state = NewMeetingState.initialState(CurrentTimeProvider.Preview).copy(
+ confirmedContacts = buildContacts(names.size),
+ continueButtonEnabled = true,
+ ),
)
}
@@ -305,7 +549,11 @@ fun PreviewNewMeetingScreen_Schedule() = WireTheme {
NewMeetingContent(
titleState = rememberTextFieldState(),
type = NewMeetingType.Schedule,
- state = NewMeetingState(),
+ state = NewMeetingState.initialState(CurrentTimeProvider.Preview).copy(
+ startTime = getNextFullHour(CurrentTimeProvider.Preview.invoke()),
+ endTime = getNextFullHour(CurrentTimeProvider.Preview.invoke()).plus(1.hours),
+ repeatingInterval = MeetingItem.RepeatingInterval.Weekly,
+ ),
)
}
diff --git a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingViewModel.kt b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingViewModel.kt
index 326d95c21e..8e8e9d078d 100644
--- a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingViewModel.kt
+++ b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingViewModel.kt
@@ -25,18 +25,31 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.ramcosta.composedestinations.generated.meetings.navArgs
+import com.wire.android.feature.meetings.model.MeetingItem
+import com.wire.android.feature.meetings.ui.create.NewMeetingState.Companion.initialState
import com.wire.android.feature.meetings.ui.create.NewMeetingViewModel.Companion.MEETING_NAME_MAX_COUNT
import com.wire.android.model.Contact
import com.wire.android.ui.common.ActionsManager
import com.wire.android.ui.common.ActionsViewModel
import com.wire.android.ui.common.textfield.textAsFlow
+import com.wire.android.util.CurrentTimeProvider
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toPersistentSet
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch
+import kotlinx.datetime.DateTimeUnit
+import kotlinx.datetime.Instant
+import kotlinx.datetime.LocalDateTime
+import kotlinx.datetime.TimeZone
+import kotlinx.datetime.plus
+import kotlinx.datetime.toInstant
+import kotlinx.datetime.toLocalDateTime
+import kotlin.time.Duration.Companion.hours
interface NewMeetingViewModel : ActionsManager {
+ val currentTimeProvider: CurrentTimeProvider
val type: NewMeetingType
val titleTextState: TextFieldState
val state: NewMeetingState
@@ -44,6 +57,9 @@ interface NewMeetingViewModel : ActionsManager {
fun updateSelectedContact(selected: Boolean, contact: Contact) {}
fun confirmSelectedContacts() {}
fun resetSelectedContacts() {}
+ fun updateStartTime(startTime: Instant) {}
+ fun updateEndTime(endTime: Instant) {}
+ fun updateRepeatingInterval(interval: MeetingItem.RepeatingInterval) {}
fun createMeeting() {}
companion object {
@@ -54,25 +70,28 @@ interface NewMeetingViewModel : ActionsManager {
class NewMeetingViewModelPreview(
override val type: NewMeetingType
) : NewMeetingViewModel {
+ override val currentTimeProvider: CurrentTimeProvider = CurrentTimeProvider.Preview
override val titleTextState: TextFieldState = TextFieldState()
- override val state: NewMeetingState = NewMeetingState()
+ override val state: NewMeetingState = initialState(currentTimeProvider)
}
class NewMeetingViewModelImpl(
- savedStateHandle: SavedStateHandle
+ savedStateHandle: SavedStateHandle,
+ override val currentTimeProvider: CurrentTimeProvider,
) : ActionsViewModel(), NewMeetingViewModel {
val navArgs: NewMeetingNavArgs = savedStateHandle.navArgs()
override val type: NewMeetingType = navArgs.type
override val titleTextState: TextFieldState = TextFieldState()
- override var state: NewMeetingState by mutableStateOf(NewMeetingState())
+ override var state: NewMeetingState by mutableStateOf(initialState(currentTimeProvider))
private set
init {
viewModelScope.launch {
- titleTextState.textAsFlow().collectLatest {
- if (state.titleError != null) validateTitle()
- validateContinueButton()
- }
+ titleTextState.textAsFlow()
+ .drop(1) // drop initial value to avoid showing error on start
+ .collectLatest {
+ validateTitle()
+ }
}
}
@@ -93,8 +112,18 @@ class NewMeetingViewModelImpl(
state = state.copy(selectedContacts = state.confirmedContacts)
}
- private fun validateContinueButton() {
- state = state.copy(continueButtonEnabled = titleTextState.text.isNotEmpty())
+ override fun updateStartTime(startTime: Instant) {
+ state = state.copy(startTime = startTime)
+ validateStartAndEndTime()
+ }
+
+ override fun updateEndTime(endTime: Instant) {
+ state = state.copy(endTime = endTime)
+ validateStartAndEndTime()
+ }
+
+ override fun updateRepeatingInterval(interval: MeetingItem.RepeatingInterval) {
+ state = state.copy(repeatingInterval = interval)
}
private fun validateTitle(): Boolean {
@@ -104,30 +133,93 @@ class NewMeetingViewModelImpl(
titleTextState.text.length > MEETING_NAME_MAX_COUNT -> NewMeetingState.TitleError.TitleExceedsLimitError
else -> null
}
- )
+ ).withContinueButtonState()
return state.titleError == null
}
+ private fun validateStartAndEndTime(): Boolean {
+ state = state.copy(
+ startTimeError = when {
+ state.startTime < currentTimeProvider() -> NewMeetingState.TimeError.StartTimeInPastError
+ else -> null
+ },
+ endTimeError = when {
+ state.endTime < currentTimeProvider() -> NewMeetingState.TimeError.EndTimeInPastError
+ state.endTime < state.startTime -> NewMeetingState.TimeError.EndTimeBeforeStartTimeError
+ else -> null
+ }
+ ).withContinueButtonState()
+ return state.startTimeError == null && state.endTimeError == null
+ }
+
+ private fun NewMeetingState.withContinueButtonState(): NewMeetingState = copy(
+ continueButtonEnabled = titleTextState.text.isNotEmpty() &&
+ titleError == null &&
+ startTimeError == null &&
+ endTimeError == null
+ )
+
override fun createMeeting() {
- if (validateTitle()) {
+ val titleValid = validateTitle()
+ val startAndEndTimeValid = validateStartAndEndTime()
+ if (titleValid && startAndEndTimeValid) {
// TODO implement meeting creation
sendAction(NewMeetingViewActions.Success)
}
}
}
+internal fun getNextFullHour(now: Instant, timeZone: TimeZone = TimeZone.currentSystemDefault()): Instant {
+ val localNow = now.toLocalDateTime(timeZone)
+ val hasPassedTime = localNow.minute > 0 || localNow.second > 0 || localNow.nanosecond > 0
+ val targetDateTime = if (hasPassedTime) {
+ val futureHour = now.plus(1, DateTimeUnit.HOUR, timeZone)
+ val localFuture = futureHour.toLocalDateTime(timeZone)
+ LocalDateTime(
+ year = localFuture.year,
+ monthNumber = localFuture.monthNumber,
+ dayOfMonth = localFuture.dayOfMonth,
+ hour = localFuture.hour,
+ minute = 0,
+ second = 0,
+ nanosecond = 0
+ )
+ } else {
+ localNow
+ }
+ return targetDateTime.toInstant(timeZone)
+}
+
@Stable
data class NewMeetingState(
val selectedContacts: ImmutableSet = persistentSetOf(),
val confirmedContacts: ImmutableSet = persistentSetOf(),
val continueButtonEnabled: Boolean = false,
val titleError: TitleError? = null,
+ val startTime: Instant,
+ val startTimeError: TimeError? = null,
+ val endTime: Instant,
+ val endTimeError: TimeError? = null,
+ val repeatingInterval: MeetingItem.RepeatingInterval = MeetingItem.RepeatingInterval.Never,
) {
@Stable
sealed interface TitleError {
data object TitleEmptyError : TitleError
data object TitleExceedsLimitError : TitleError
}
+
+ sealed interface TimeError {
+ data object StartTimeInPastError : TimeError
+ data object EndTimeInPastError : TimeError
+ data object EndTimeBeforeStartTimeError : TimeError
+ }
+
+ companion object {
+ fun initialState(currentTimeProvider: CurrentTimeProvider): NewMeetingState {
+ val startTime = getNextFullHour(currentTimeProvider())
+ return NewMeetingState(startTime = startTime, endTime = startTime.plus(1.hours))
+ }
+ }
}
sealed interface NewMeetingViewActions {
diff --git a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/list/MeetingItem.kt b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/list/MeetingItem.kt
index 98539b635f..374e94e08d 100644
--- a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/list/MeetingItem.kt
+++ b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/list/MeetingItem.kt
@@ -198,14 +198,14 @@ private fun MeetingOngoingDurationTimeSublineText(startedTime: Instant) {
}
@Composable
-private fun RepeatingIntervalInfoLabel(repeatingInterval: RepeatingInterval?) {
- repeatingInterval?.let {
+private fun RepeatingIntervalInfoLabel(repeatingInterval: RepeatingInterval) {
+ if (repeatingInterval != RepeatingInterval.Never) {
WireItemLabel(text = stringResource(repeatingInterval.nameResId), textStyle = typography().label01)
}
}
@Composable
-private fun MeetingTimeInfoRow(status: Status, repeatingInterval: RepeatingInterval?) {
+private fun MeetingTimeInfoRow(status: Status, repeatingInterval: RepeatingInterval) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(dimensions().spacing3x)
diff --git a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/mock/MeetingItemMocks.kt b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/mock/MeetingItemMocks.kt
index c210b84217..a1f71858da 100644
--- a/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/mock/MeetingItemMocks.kt
+++ b/features/meetings/src/main/java/com/wire/android/feature/meetings/ui/mock/MeetingItemMocks.kt
@@ -53,7 +53,7 @@ val CurrentTimeProvider.endedPrivateChannelMeeting
conversationId = ConversationId("cid1", "domain"),
title = "Ended Private Channel Meeting",
belongingType = BelongingType.Channel(name = "Private Channel Name", isPrivateChannel = true),
- repeatingInterval = null,
+ repeatingInterval = MeetingItem.RepeatingInterval.Never,
selfRole = MeetingItem.SelfRole.Admin,
status = Status.Ended(
startTime = currentTime().fullMinutes().minus(1.days).minus(120.minutes),
@@ -67,7 +67,7 @@ val CurrentTimeProvider.ongoingAttendingOneOnOneMeeting
conversationId = ConversationId("cid2", "domain"),
title = "Ongoing Attending 1:1 Meeting",
belongingType = BelongingType.OneOnOne(username = "John Doe", avatar = UserAvatarData()),
- repeatingInterval = null,
+ repeatingInterval = MeetingItem.RepeatingInterval.Never,
selfRole = MeetingItem.SelfRole.Admin,
status = Status.Ongoing(
startTime = currentTime().fullMinutes().minus(15.minutes),
@@ -94,7 +94,7 @@ val CurrentTimeProvider.grouplessOngoingMeeting
meetingId = "id3",
conversationId = ConversationId("cid3", "domain"),
title = "Groupless Ongoing Meeting",
- repeatingInterval = null,
+ repeatingInterval = MeetingItem.RepeatingInterval.Never,
belongingType = BelongingType.Groupless(avatars = avatars, limit = 5),
selfRole = MeetingItem.SelfRole.Admin,
status = Status.Ongoing(
@@ -108,7 +108,7 @@ val CurrentTimeProvider.scheduledChannelMeetingStartingSoon
meetingId = "id4",
conversationId = ConversationId("cid4", "domain"),
title = "Scheduled Channel Meeting Starting Soon",
- repeatingInterval = null,
+ repeatingInterval = MeetingItem.RepeatingInterval.Never,
belongingType = BelongingType.Channel(name = "Channel Name", isPrivateChannel = false),
selfRole = MeetingItem.SelfRole.Admin,
status = Status.Scheduled(
@@ -138,7 +138,7 @@ val CurrentTimeProvider.pastMeetingMocks
meetingId = "past1",
conversationId = ConversationId("cid", "domain"),
title = "Ended Groupless Meeting",
- repeatingInterval = null,
+ repeatingInterval = MeetingItem.RepeatingInterval.Never,
belongingType = BelongingType.Groupless(avatars = avatars, limit = 5),
selfRole = MeetingItem.SelfRole.Admin,
status = Status.Ended(
@@ -150,7 +150,7 @@ val CurrentTimeProvider.pastMeetingMocks
meetingId = "past2",
conversationId = ConversationId("cid", "domain"),
title = "Ended Channel Meeting",
- repeatingInterval = null,
+ repeatingInterval = MeetingItem.RepeatingInterval.Never,
belongingType = BelongingType.Channel(name = "Channel Name", isPrivateChannel = false),
selfRole = MeetingItem.SelfRole.Admin,
status = Status.Ended(
@@ -162,7 +162,7 @@ val CurrentTimeProvider.pastMeetingMocks
meetingId = "past3",
conversationId = ConversationId("cid", "domain"),
title = "Ended 1:1 Meeting",
- repeatingInterval = null,
+ repeatingInterval = MeetingItem.RepeatingInterval.Never,
belongingType = BelongingType.OneOnOne(username = "John Doe", avatar = UserAvatarData()),
selfRole = MeetingItem.SelfRole.Admin,
status = Status.Ended(
@@ -174,7 +174,7 @@ val CurrentTimeProvider.pastMeetingMocks
meetingId = "past4",
conversationId = ConversationId("cid", "domain"),
title = "Ended Group Meeting",
- repeatingInterval = null,
+ repeatingInterval = MeetingItem.RepeatingInterval.Never,
belongingType = BelongingType.Group(name = "Group Name"),
selfRole = MeetingItem.SelfRole.Admin,
status = Status.Ended(
@@ -186,7 +186,7 @@ val CurrentTimeProvider.pastMeetingMocks
meetingId = "past5",
conversationId = ConversationId("cid", "domain"),
title = "Ended Channel Meeting",
- repeatingInterval = null,
+ repeatingInterval = MeetingItem.RepeatingInterval.Never,
belongingType = BelongingType.Channel(name = "Channel Name", isPrivateChannel = true),
selfRole = MeetingItem.SelfRole.Admin,
status = Status.Ended(
@@ -198,7 +198,7 @@ val CurrentTimeProvider.pastMeetingMocks
meetingId = "past6",
conversationId = ConversationId("cid", "domain"),
title = "Ended Groupless Meeting",
- repeatingInterval = null,
+ repeatingInterval = MeetingItem.RepeatingInterval.Never,
belongingType = BelongingType.Groupless(avatars = avatars.take(2).toPersistentList(), limit = 5),
selfRole = MeetingItem.SelfRole.Admin,
status = Status.Scheduled(
@@ -242,7 +242,7 @@ val CurrentTimeProvider.nextMeetingMocks
meetingId = "next4",
conversationId = ConversationId("cid", "domain"),
title = "Scheduled Groupless Meeting",
- repeatingInterval = null,
+ repeatingInterval = MeetingItem.RepeatingInterval.Never,
belongingType = BelongingType.Groupless(avatars = avatars.take(2).toPersistentList(), limit = 5),
selfRole = MeetingItem.SelfRole.Admin,
status = Status.Scheduled(
@@ -330,7 +330,7 @@ data class Meeting(
val title: String,
val startTime: Instant,
val endTime: Instant?,
- val repeatingInterval: MeetingItem.RepeatingInterval?,
+ val repeatingInterval: MeetingItem.RepeatingInterval,
val ongoingCallStatus: MeetingItem.OngoingCallStatus?,
val selfRole: SelfRole,
)
diff --git a/features/meetings/src/main/res/values/strings.xml b/features/meetings/src/main/res/values/strings.xml
index 5c54be3ee3..052bd6171c 100644
--- a/features/meetings/src/main/res/values/strings.xml
+++ b/features/meetings/src/main/res/values/strings.xml
@@ -18,6 +18,7 @@
NEXT
PAST
+ Never
Daily
Weekly
Biweekly
@@ -64,6 +65,18 @@
Close select participants view
Please enter a meeting name
Meeting name should not exceed 64 characters
+ Starts
+ Select start date
+ Select start time
+ Ends
+ Select end date
+ Select end time
+ Start time cannot be in the past
+ End time cannot be in the past
+ End time cannot be before start time
+ Repeats
+ Select repeat option
+ Allow guests
- +%1$d more
diff --git a/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/create/NewMeetingViewModelTest.kt b/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/create/NewMeetingViewModelTest.kt
new file mode 100644
index 0000000000..5c12e1b0ad
--- /dev/null
+++ b/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/create/NewMeetingViewModelTest.kt
@@ -0,0 +1,291 @@
+/*
+ * Wire
+ * Copyright (C) 2026 Wire Swiss GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see http://www.gnu.org/licenses/.
+ */
+package com.wire.android.feature.meetings.ui.create
+
+import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
+import androidx.lifecycle.SavedStateHandle
+import app.cash.turbine.test
+import com.ramcosta.composedestinations.generated.meetings.navArgs
+import com.wire.android.config.CoroutineTestExtension
+import com.wire.android.config.NavigationTestExtension
+import com.wire.android.config.SnapshotExtension
+import com.wire.android.feature.meetings.model.MeetingItem
+import com.wire.android.model.Contact
+import com.wire.android.ui.home.conversationslist.model.Membership
+import com.wire.android.util.CurrentTimeProvider
+import com.wire.kalium.logic.data.user.ConnectionState
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import kotlinx.datetime.Instant
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.junit.jupiter.api.Assertions.assertNull
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import kotlin.time.Duration.Companion.hours
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@ExtendWith(CoroutineTestExtension::class, NavigationTestExtension::class, SnapshotExtension::class)
+class NewMeetingViewModelTest {
+ private val dispatcher = StandardTestDispatcher()
+
+ @BeforeEach
+ fun setUp() {
+ Dispatchers.setMain(dispatcher)
+ }
+
+ @AfterEach
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
+ @Test
+ fun givenScheduleTypeAndCurrentTime_whenViewModelIsCreated_thenStateIsInitialized() = runTest(dispatcher) {
+ val currentTime = Instant.parse("2026-01-01T12:00:00Z")
+ val (_, viewModel) = arrangeViewModel(
+ Arrangement(dispatcher)
+ .withNewMeetingType(NewMeetingType.Schedule)
+ .withCurrentTimeProvider { currentTime }
+ )
+
+ assertEquals(NewMeetingType.Schedule, viewModel.type)
+ assertEquals(currentTime, viewModel.state.startTime)
+ assertEquals(currentTime + 1.hours, viewModel.state.endTime)
+ assertFalse(viewModel.state.continueButtonEnabled)
+ assertNull(viewModel.state.titleError)
+ assertNull(viewModel.state.startTimeError)
+ assertNull(viewModel.state.endTimeError)
+ }
+
+ @Test
+ fun givenSelectedContacts_whenContactsAreConfirmedAndReset_thenStateKeepsConfirmedContacts() = runTest(dispatcher) {
+ val contact = contact("contact-1")
+ val otherContact = contact("contact-2")
+ val (_, viewModel) = arrangeViewModel()
+
+ viewModel.updateSelectedContact(selected = true, contact = contact)
+ viewModel.updateSelectedContact(selected = true, contact = otherContact)
+ assertEquals(setOf(contact, otherContact), viewModel.state.selectedContacts.toSet())
+ assertEquals(emptySet(), viewModel.state.confirmedContacts.toSet())
+
+ viewModel.confirmSelectedContacts()
+ assertEquals(setOf(contact, otherContact), viewModel.state.confirmedContacts.toSet())
+
+ viewModel.updateSelectedContact(selected = false, contact = otherContact)
+ assertEquals(setOf(contact), viewModel.state.selectedContacts.toSet())
+
+ viewModel.resetSelectedContacts()
+ assertEquals(setOf(contact, otherContact), viewModel.state.selectedContacts.toSet())
+ }
+
+ @Test
+ fun givenRepeatingInterval_whenIntervalIsUpdated_thenStateIsUpdated() = runTest(dispatcher) {
+ val (_, viewModel) = arrangeViewModel()
+
+ viewModel.updateRepeatingInterval(MeetingItem.RepeatingInterval.Weekly)
+
+ assertEquals(MeetingItem.RepeatingInterval.Weekly, viewModel.state.repeatingInterval)
+ }
+
+ @Test
+ fun givenInitialEmptyTitle_whenViewModelIsCreated_thenTitleErrorIsNotShownAndContinueIsDisabled() = runTest(dispatcher) {
+ val (_, viewModel) = arrangeViewModel()
+
+ assertNull(viewModel.state.titleError)
+ assertFalse(viewModel.state.continueButtonEnabled)
+ }
+
+ @Test
+ fun givenValidTitle_whenTitleChanges_thenContinueIsEnabled() = runTest(dispatcher) {
+ val (_, viewModel) = arrangeViewModel()
+
+ enterTitle(viewModel, "Weekly sync")
+
+ assertNull(viewModel.state.titleError)
+ assertEquals(true, viewModel.state.continueButtonEnabled)
+ }
+
+ @Test
+ fun givenTitleIsClearedAfterInput_whenTitleChanges_thenEmptyTitleErrorIsShown() = runTest(dispatcher) {
+ val (_, viewModel) = arrangeViewModel()
+
+ enterTitle(viewModel, "Weekly sync")
+ enterTitle(viewModel, "")
+
+ assertEquals(NewMeetingState.TitleError.TitleEmptyError, viewModel.state.titleError)
+ assertFalse(viewModel.state.continueButtonEnabled)
+ }
+
+ @Test
+ fun givenTitleExceedsLimit_whenTitleChanges_thenTitleExceedsLimitErrorIsShown() = runTest(dispatcher) {
+ val (_, viewModel) = arrangeViewModel()
+
+ enterTitle(viewModel, "a".repeat(NewMeetingViewModel.MEETING_NAME_MAX_COUNT + 1))
+
+ assertEquals(NewMeetingState.TitleError.TitleExceedsLimitError, viewModel.state.titleError)
+ assertFalse(viewModel.state.continueButtonEnabled)
+ }
+
+ @Test
+ fun givenStartTimeInPast_whenStartTimeChanges_thenStartTimeInPastErrorIsShown() = runTest(dispatcher) {
+ val currentTime = Instant.parse("2026-01-01T12:00:00Z")
+ val (_, viewModel) = arrangeViewModel(Arrangement(dispatcher).withCurrentTimeProvider { currentTime })
+
+ enterTitle(viewModel, "Weekly sync")
+ viewModel.updateStartTime(currentTime - 1.hours)
+ advanceUntilIdle()
+
+ assertEquals(NewMeetingState.TimeError.StartTimeInPastError, viewModel.state.startTimeError)
+ assertFalse(viewModel.state.continueButtonEnabled)
+ }
+
+ @Test
+ fun givenEndTimeInPast_whenEndTimeChanges_thenEndTimeInPastErrorIsShown() = runTest(dispatcher) {
+ val currentTime = Instant.parse("2026-01-01T12:00:00Z")
+ val (_, viewModel) = arrangeViewModel(Arrangement(dispatcher).withCurrentTimeProvider { currentTime })
+
+ enterTitle(viewModel, "Weekly sync")
+ viewModel.updateEndTime(currentTime - 1.hours)
+ advanceUntilIdle()
+
+ assertEquals(NewMeetingState.TimeError.EndTimeInPastError, viewModel.state.endTimeError)
+ assertFalse(viewModel.state.continueButtonEnabled)
+ }
+
+ @Test
+ fun givenEndTimeBeforeStartTime_whenEndTimeChanges_thenEndTimeBeforeStartTimeErrorIsShown() = runTest(dispatcher) {
+ val currentTime = Instant.parse("2026-01-01T12:00:00Z")
+ val (_, viewModel) = arrangeViewModel(Arrangement(dispatcher).withCurrentTimeProvider { currentTime })
+
+ enterTitle(viewModel, "Weekly sync")
+ viewModel.updateStartTime(currentTime + 2.hours)
+ viewModel.updateEndTime(currentTime + 1.hours)
+ advanceUntilIdle()
+
+ assertEquals(NewMeetingState.TimeError.EndTimeBeforeStartTimeError, viewModel.state.endTimeError)
+ assertFalse(viewModel.state.continueButtonEnabled)
+ }
+
+ @Test
+ fun givenInvalidTimesBecomeValid_whenTimesChange_thenErrorsAreClearedAndContinueIsEnabled() = runTest(dispatcher) {
+ val currentTime = Instant.parse("2026-01-01T12:00:00Z")
+ val (_, viewModel) = arrangeViewModel(Arrangement(dispatcher).withCurrentTimeProvider { currentTime })
+
+ enterTitle(viewModel, "Weekly sync")
+ viewModel.updateStartTime(currentTime + 2.hours)
+ viewModel.updateEndTime(currentTime + 1.hours)
+ advanceUntilIdle()
+ viewModel.updateEndTime(currentTime + 3.hours)
+ advanceUntilIdle()
+
+ assertNull(viewModel.state.startTimeError)
+ assertNull(viewModel.state.endTimeError)
+ assertEquals(true, viewModel.state.continueButtonEnabled)
+ }
+
+ @Test
+ fun givenValidData_whenCreateMeetingIsCalled_thenSuccessActionIsSent() = runTest(dispatcher) {
+ val currentTime = Instant.parse("2026-01-01T12:00:00Z")
+ val (_, viewModel) = arrangeViewModel(Arrangement(dispatcher).withCurrentTimeProvider { currentTime })
+
+ enterTitle(viewModel, "Weekly sync")
+
+ viewModel.actions.test {
+ viewModel.createMeeting()
+ advanceUntilIdle()
+
+ assertEquals(NewMeetingViewActions.Success, awaitItem())
+ }
+ }
+
+ @Test
+ fun givenInvalidTitle_whenCreateMeetingIsCalled_thenTitleErrorIsShownAndSuccessActionIsNotSent() = runTest(dispatcher) {
+ val (_, viewModel) = arrangeViewModel()
+
+ viewModel.actions.test {
+ viewModel.createMeeting()
+ advanceUntilIdle()
+
+ expectNoEvents()
+ assertEquals(NewMeetingState.TitleError.TitleEmptyError, viewModel.state.titleError)
+ assertFalse(viewModel.state.continueButtonEnabled)
+ }
+ }
+
+ private fun TestScope.arrangeViewModel(
+ arrangement: Arrangement = Arrangement(dispatcher)
+ ): Pair =
+ arrangement.arrange().also { runCurrent() }
+
+ private fun TestScope.enterTitle(viewModel: NewMeetingViewModel, title: String) {
+ viewModel.titleTextState.setTextAndPlaceCursorAtEnd(title)
+ advanceUntilIdle()
+ }
+
+ private fun contact(id: String) = Contact(
+ id = id,
+ domain = "wire.com",
+ name = "Contact $id",
+ handle = id,
+ membership = Membership.Standard,
+ connectionState = ConnectionState.ACCEPTED,
+ )
+
+ private class Arrangement(private val dispatcher: TestDispatcher) {
+ var currentTimeProvider = CurrentTimeProvider {
+ Instant.fromEpochMilliseconds(dispatcher.scheduler.currentTime)
+ }
+
+ @MockK
+ private lateinit var savedStateHandle: SavedStateHandle
+
+ private var newMeetingType: NewMeetingType = NewMeetingType.MeetNow
+
+ init {
+ MockKAnnotations.init(this)
+ every {
+ savedStateHandle.navArgs()
+ } answers { NewMeetingNavArgs(type = newMeetingType) }
+ }
+
+ fun withNewMeetingType(type: NewMeetingType) = apply {
+ newMeetingType = type
+ }
+ fun withCurrentTimeProvider(currentTime: () -> Instant) = apply {
+ currentTimeProvider = CurrentTimeProvider(currentTime)
+ }
+
+ fun arrange() = this to NewMeetingViewModelImpl(
+ savedStateHandle = savedStateHandle,
+ currentTimeProvider = currentTimeProvider,
+ )
+ }
+}
diff --git a/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/list/MeetingListViewModelTest.kt b/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/list/MeetingListViewModelTest.kt
index 8932ea50c0..c4c5b25401 100644
--- a/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/list/MeetingListViewModelTest.kt
+++ b/features/meetings/src/test/kotlin/com/wire/android/feature/meetings/ui/list/MeetingListViewModelTest.kt
@@ -152,7 +152,7 @@ class MeetingListViewModelTest {
title = "Meeting",
startTime = startTime,
endTime = startTime + 30.minutes,
- repeatingInterval = null,
+ repeatingInterval = MeetingItem.RepeatingInterval.Never,
ongoingCallStatus = null,
selfRole = MeetingItem.SelfRole.Admin,
)
diff --git a/features/meetings/stability/meetings-debug.stability b/features/meetings/stability/meetings-debug.stability
index ecf91e9adf..9f51941cf8 100644
--- a/features/meetings/stability/meetings-debug.stability
+++ b/features/meetings/stability/meetings-debug.stability
@@ -36,7 +36,7 @@ public fun com.wire.android.feature.meetings.ui.NewMeetingBottomSheet(sheetState
- onScheduleClick: STABLE (function type)
@Composable
-public fun com.wire.android.feature.meetings.ui.create.NewMeetingContent(state: com.wire.android.feature.meetings.ui.create.NewMeetingState, titleState: androidx.compose.foundation.text.input.TextFieldState, type: com.wire.android.feature.meetings.ui.create.NewMeetingType, modifier: androidx.compose.ui.Modifier, onBackPressed: kotlin.Function0, onParticipantsClicked: kotlin.Function0, onCreateClicked: kotlin.Function0): kotlin.Unit
+public fun com.wire.android.feature.meetings.ui.create.NewMeetingContent(state: com.wire.android.feature.meetings.ui.create.NewMeetingState, titleState: androidx.compose.foundation.text.input.TextFieldState, type: com.wire.android.feature.meetings.ui.create.NewMeetingType, modifier: androidx.compose.ui.Modifier, onBackPressed: kotlin.Function0, onParticipantsClicked: kotlin.Function0, onCreateClicked: kotlin.Function0, onStartTimeChanged: kotlin.Function1<@[ParameterName(name = \, onEndTimeChanged: kotlin.Function1<@[ParameterName(name = \, onRepeatingIntervalChanged: kotlin.Function1<@[ParameterName(name = \): kotlin.Unit
skippable: true
restartable: true
params:
@@ -47,6 +47,9 @@ public fun com.wire.android.feature.meetings.ui.create.NewMeetingContent(state:
- onBackPressed: STABLE (function type)
- onParticipantsClicked: STABLE (function type)
- onCreateClicked: STABLE (function type)
+ - onStartTimeChanged: STABLE (function type)
+ - onEndTimeChanged: STABLE (function type)
+ - onRepeatingIntervalChanged: STABLE (function type)
@Composable
public fun com.wire.android.feature.meetings.ui.create.NewMeetingParticipantsScreen(navigator: com.wire.android.feature.meetings.navigation.MeetingNavigator, newMeetingViewModel: com.wire.android.feature.meetings.ui.create.NewMeetingViewModel): kotlin.Unit
@@ -73,6 +76,15 @@ private fun com.wire.android.feature.meetings.ui.create.ParticipantsInput(partic
- participants: STABLE (known stable type)
- onClick: STABLE (function type)
+@Composable
+private fun com.wire.android.feature.meetings.ui.create.RepeatingIntervalDropDown(repeatingInterval: com.wire.android.feature.meetings.model.MeetingItem.RepeatingInterval, onRepeatingIntervalChanged: kotlin.Function1, items: kotlin.collections.List): kotlin.Unit
+ skippable: false
+ restartable: true
+ params:
+ - repeatingInterval: STABLE (marked @Stable or @Immutable)
+ - onRepeatingIntervalChanged: STABLE (function type)
+ - items: RUNTIME (requires runtime check)
+
@Composable
private fun com.wire.android.feature.meetings.ui.create.SelectButton(onClick: kotlin.Function0, modifier: androidx.compose.ui.Modifier, buttonModifier: androidx.compose.ui.Modifier, leadingIcon: @[Composable] androidx.compose.runtime.internal.ComposableFunction0?, elevation: androidx.compose.ui.unit.Dp): kotlin.Unit
skippable: true
@@ -84,6 +96,18 @@ private fun com.wire.android.feature.meetings.ui.create.SelectButton(onClick: ko
- leadingIcon: STABLE (composable function type)
- elevation: STABLE (marked @Stable or @Immutable)
+@Composable
+private fun com.wire.android.feature.meetings.ui.create.TimeInput(time: kotlinx.datetime.Instant, timeError: com.wire.android.feature.meetings.ui.create.NewMeetingState.TimeError?, onTimeChanged: kotlin.Function1, label: kotlin.String, datePlaceholder: kotlin.String, timePlaceholder: kotlin.String): kotlin.Unit
+ skippable: true
+ restartable: true
+ params:
+ - time: STABLE (matched by stability configuration)
+ - timeError: STABLE (class with no mutable properties)
+ - onTimeChanged: STABLE (function type)
+ - label: STABLE (String is immutable)
+ - datePlaceholder: STABLE (String is immutable)
+ - timePlaceholder: STABLE (String is immutable)
+
@Composable
private fun com.wire.android.feature.meetings.ui.create.TitleInput(titleState: androidx.compose.foundation.text.input.TextFieldState, titleError: com.wire.android.feature.meetings.ui.create.NewMeetingState.TitleError?): kotlin.Unit
skippable: true
@@ -127,7 +151,7 @@ private fun com.wire.android.feature.meetings.ui.list.MeetingBelongingInfoRow(co
restartable: true
params:
- conversationId: STABLE (matched by stability configuration)
- - type: STABLE (class with no mutable properties)
+ - type: STABLE (marked @Stable or @Immutable)
@Composable
public fun com.wire.android.feature.meetings.ui.list.MeetingHeader(header: com.wire.android.feature.meetings.model.MeetingHeader, modifier: androidx.compose.ui.Modifier): kotlin.Unit
@@ -139,10 +163,10 @@ public fun com.wire.android.feature.meetings.ui.list.MeetingHeader(header: com.w
@Composable
public fun com.wire.android.feature.meetings.ui.list.MeetingItem(meeting: com.wire.android.feature.meetings.model.MeetingItem, modifier: androidx.compose.ui.Modifier, openMeetingOptions: kotlin.Function1<@[ParameterName(name = \): kotlin.Unit
- skippable: false
+ skippable: true
restartable: true
params:
- - meeting: UNSTABLE (has mutable properties or unstable members)
+ - meeting: STABLE (marked @Stable or @Immutable)
- modifier: STABLE (marked @Stable or @Immutable)
- openMeetingOptions: STABLE (function type)
@@ -185,10 +209,10 @@ private fun com.wire.android.feature.meetings.ui.list.MeetingMoreButton(onMenuCl
@Composable
private fun com.wire.android.feature.meetings.ui.list.MeetingOngoingAttendingRow(status: com.wire.android.feature.meetings.model.MeetingItem.Status, onJoinClick: kotlin.Function0): kotlin.Unit
- skippable: false
+ skippable: true
restartable: true
params:
- - status: UNSTABLE (has mutable properties or unstable members)
+ - status: STABLE (marked @Stable or @Immutable)
- onJoinClick: STABLE (function type)
@Composable
@@ -214,12 +238,12 @@ private fun com.wire.android.feature.meetings.ui.list.MeetingStartingInPrimaryBo
- startTime: STABLE (matched by stability configuration)
@Composable
-private fun com.wire.android.feature.meetings.ui.list.MeetingTimeInfoRow(status: com.wire.android.feature.meetings.model.MeetingItem.Status, repeatingInterval: com.wire.android.feature.meetings.model.MeetingItem.RepeatingInterval?): kotlin.Unit
- skippable: false
+private fun com.wire.android.feature.meetings.ui.list.MeetingTimeInfoRow(status: com.wire.android.feature.meetings.model.MeetingItem.Status, repeatingInterval: com.wire.android.feature.meetings.model.MeetingItem.RepeatingInterval): kotlin.Unit
+ skippable: true
restartable: true
params:
- - status: UNSTABLE (has mutable properties or unstable members)
- - repeatingInterval: STABLE (class with no mutable properties)
+ - status: STABLE (marked @Stable or @Immutable)
+ - repeatingInterval: STABLE (marked @Stable or @Immutable)
@Composable
private fun com.wire.android.feature.meetings.ui.list.PrimaryBodyText(text: kotlin.String): kotlin.Unit
@@ -229,11 +253,11 @@ private fun com.wire.android.feature.meetings.ui.list.PrimaryBodyText(text: kotl
- text: STABLE (String is immutable)
@Composable
-private fun com.wire.android.feature.meetings.ui.list.RepeatingIntervalInfoLabel(repeatingInterval: com.wire.android.feature.meetings.model.MeetingItem.RepeatingInterval?): kotlin.Unit
+private fun com.wire.android.feature.meetings.ui.list.RepeatingIntervalInfoLabel(repeatingInterval: com.wire.android.feature.meetings.model.MeetingItem.RepeatingInterval): kotlin.Unit
skippable: true
restartable: true
params:
- - repeatingInterval: STABLE (class with no mutable properties)
+ - repeatingInterval: STABLE (marked @Stable or @Immutable)
@Composable
private fun com.wire.android.feature.meetings.ui.list.SublineText(text: kotlin.String): kotlin.Unit
@@ -279,10 +303,10 @@ public fun com.wire.android.feature.meetings.ui.newMeetingViewModel(viewModelSto
@Composable
private fun com.wire.android.feature.meetings.ui.options.MeetingOptionsModalContent(meeting: com.wire.android.feature.meetings.model.MeetingItem, onStartMeeting: kotlin.Function0, onCreateConversation: kotlin.Function0, onCopyLink: kotlin.Function0, onEditMeeting: kotlin.Function0, onDeleteMeetingForMe: kotlin.Function0, onDeleteMeetingForEveryone: kotlin.Function0): kotlin.Unit
- skippable: false
+ skippable: true
restartable: true
params:
- - meeting: UNSTABLE (has mutable properties or unstable members)
+ - meeting: STABLE (marked @Stable or @Immutable)
- onStartMeeting: STABLE (function type)
- onCreateConversation: STABLE (function type)
- onCopyLink: STABLE (function type)