diff --git a/app/src/main/kotlin/com/wire/android/ui/common/WireRadioButton.kt b/app/src/main/kotlin/com/wire/android/ui/common/WireRadioButton.kt index 6fda2199d04..240b80a8104 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/WireRadioButton.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/WireRadioButton.kt @@ -29,9 +29,9 @@ import com.wire.android.util.ui.PreviewMultipleThemes @Composable fun WireRadioButton( checked: Boolean, - onButtonChecked: (() -> Unit), + modifier: Modifier = Modifier, + onButtonChecked: (() -> Unit)? = null, enabled: Boolean = true, - modifier: Modifier = Modifier ) { RadioButton( modifier = modifier, diff --git a/app/src/main/kotlin/com/wire/android/ui/common/groupname/GroupMetadataState.kt b/app/src/main/kotlin/com/wire/android/ui/common/groupname/GroupMetadataState.kt index 0389de7ba65..0802148edcd 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/groupname/GroupMetadataState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/groupname/GroupMetadataState.kt @@ -20,6 +20,7 @@ package com.wire.android.ui.common.groupname import com.wire.android.ui.home.newconversation.channelaccess.ChannelAccessType import com.wire.android.ui.home.newconversation.channelaccess.ChannelAddPermissionType +import com.wire.android.ui.home.newconversation.channelhistory.ChannelHistoryType import com.wire.android.ui.home.newconversation.model.Contact import com.wire.kalium.logic.data.conversation.CreateConversationParam import kotlinx.collections.immutable.ImmutableSet @@ -40,6 +41,7 @@ data class GroupMetadataState( val isServicesAllowed: Boolean = false, val channelAccessType: ChannelAccessType = ChannelAccessType.PRIVATE, val channelAddPermissionType: ChannelAddPermissionType = ChannelAddPermissionType.ADMINS, + val channelHistoryType: ChannelHistoryType = ChannelHistoryType.Off, val completed: Completed = Completed.None, ) { enum class Completed { diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptions.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptions.kt index 9fede912eef..9c57c6cd2bc 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptions.kt @@ -122,10 +122,12 @@ fun GroupConversationSettings( title = stringResource(R.string.channel_access_label), subtitle = stringResource(id = R.string.channel_access_short_description), arrowType = if (state.isUpdatingChannelAccessAllowed) ArrowType.TITLE_ALIGNED else ArrowType.NONE, - arrowLabel = stringResource(state.channelAccessType!!.label), + arrowLabel = stringResource(state.channelAccessType!!.labelResId), arrowLabelColor = colorsScheme().onBackground, - onClick = onChannelAccessItemClicked, - isClickable = state.isUpdatingChannelAccessAllowed, + clickable = Clickable( + enabled = state.isUpdatingChannelAccessAllowed, + onClick = onChannelAccessItemClicked, + ), ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptionsItem.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptionsItem.kt index 546a41b1e8c..dd645e47379 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptionsItem.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/options/GroupConversationOptionsItem.kt @@ -37,18 +37,20 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.wire.android.R import com.wire.android.model.Clickable import com.wire.android.ui.common.ArrowRightIcon +import com.wire.android.ui.common.WireRadioButton import com.wire.android.ui.common.button.WireSecondaryButton import com.wire.android.ui.common.clickable import com.wire.android.ui.home.settings.SettingsOptionSwitch import com.wire.android.ui.home.settings.SwitchState +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.ui.PreviewMultipleThemes @Composable fun GroupConversationOptionsItem( @@ -57,9 +59,7 @@ fun GroupConversationOptionsItem( .fillMaxWidth() .background(MaterialTheme.wireColorScheme.surface) .defaultMinSize(minHeight = MaterialTheme.wireDimensions.conversationOptionsItemMinHeight), - isClickable: Boolean = false, - onClick: () -> Unit = {}, - clickable: Clickable = Clickable(enabled = isClickable, onClick = onClick), + clickable: Clickable = Clickable(enabled = false, onClick = {}), arrowLabel: String? = null, arrowLabelColor: Color = MaterialTheme.wireColorScheme.secondaryText, subtitle: String? = null, @@ -70,7 +70,8 @@ fun GroupConversationOptionsItem( switchState: SwitchState = SwitchState.None, titleStyle: TextStyle = MaterialTheme.wireTypography.body02, arrowType: ArrowType = ArrowType.CENTER_ALIGNED, - contentDescription: String? = null + contentDescription: String? = null, + selected: Boolean? = null, ) { Row( verticalAlignment = Alignment.CenterVertically, @@ -96,6 +97,12 @@ fun GroupConversationOptionsItem( ) } Row(verticalAlignment = Alignment.CenterVertically) { + if (selected != null) { + WireRadioButton( + checked = selected, + modifier = Modifier.padding(end = MaterialTheme.wireDimensions.spacing8x), + ) + } Text( text = title, style = titleStyle, @@ -142,7 +149,6 @@ private fun ArrowRight() { Box( modifier = Modifier.padding( start = MaterialTheme.wireDimensions.spacing8x, - end = MaterialTheme.wireDimensions.spacing8x ) ) { ArrowRightIcon(contentDescription = R.string.content_description_empty) } } @@ -152,14 +158,14 @@ enum class ArrowType { } @Composable -@Preview(name = "Item with label and title") -fun PreviewGroupConversationOptionsWithLabelAndTitle() { +@PreviewMultipleThemes +fun PreviewGroupConversationOptionsWithLabelAndTitle() = WireTheme { GroupConversationOptionsItem(title = "Conversation group title", label = "GROUP NAME") } @Composable -@Preview(name = "Item with title and switch clickable") -fun PreviewGroupConversationOptionsWithTitleAndSwitchClickable() { +@PreviewMultipleThemes +fun PreviewGroupConversationOptionsWithTitleAndSwitchClickable() = WireTheme { GroupConversationOptionsItem( title = "Services", switchState = SwitchState.Enabled(value = true, onCheckedChange = {}), @@ -168,8 +174,18 @@ fun PreviewGroupConversationOptionsWithTitleAndSwitchClickable() { } @Composable -@Preview(name = "Item with title and text only switch") -fun PreviewGroupConversationOptionsWithTitleAndTextOnlySwitch() { +@PreviewMultipleThemes +fun PreviewGroupConversationOptionsWithTitleAndSwitchWithoutArrow() = WireTheme { + GroupConversationOptionsItem( + title = "Services", + switchState = SwitchState.Enabled(value = true, onCheckedChange = {}), + arrowType = ArrowType.NONE + ) +} + +@Composable +@PreviewMultipleThemes +fun PreviewGroupConversationOptionsWithTitleAndTextOnlySwitch() = WireTheme { GroupConversationOptionsItem( title = "Services", switchState = SwitchState.TextOnly(value = true), @@ -178,8 +194,8 @@ fun PreviewGroupConversationOptionsWithTitleAndTextOnlySwitch() { } @Composable -@Preview(name = "Item with title, subtitle and icon") -fun PreviewGroupConversationOptionsWithTitleAndSubtitleAndIcon() { +@PreviewMultipleThemes +fun PreviewGroupConversationOptionsWithTitleAndSubtitleAndIcon() = WireTheme { GroupConversationOptionsItem( title = "Group Color", subtitle = "Red", @@ -195,8 +211,8 @@ fun PreviewGroupConversationOptionsWithTitleAndSubtitleAndIcon() { } @Composable -@Preview(name = "Item with title, subtitle, switch and footer button") -fun PreviewGroupConversationOptionsWithTitleAndSubtitleAndSwitchAndFooterButton() { +@PreviewMultipleThemes +fun PreviewGroupConversationOptionsWithTitleAndSubtitleAndSwitchAndFooterButton() = WireTheme { GroupConversationOptionsItem( title = "Guests", subtitle = "Turn this option ON to open this conversation to people outside your team, even if they don't have Wire.", @@ -207,8 +223,8 @@ fun PreviewGroupConversationOptionsWithTitleAndSubtitleAndSwitchAndFooterButton( } @Composable -@Preview(name = "Item with title and subtitle without arrow") -fun PreviewGroupConversationOptionsWithTitleAndSubtitleWithoutArrow() { +@PreviewMultipleThemes +fun PreviewGroupConversationOptionsWithTitleAndSubtitleWithoutArrow() = WireTheme { GroupConversationOptionsItem( label = "Cipher Suite", title = "MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519(0x0001)", @@ -216,3 +232,14 @@ fun PreviewGroupConversationOptionsWithTitleAndSubtitleWithoutArrow() { arrowType = ArrowType.NONE ) } + +@Composable +@PreviewMultipleThemes +fun PreviewGroupConversationOptionsWithTitleAndArrowLabelAndRadioButton() = WireTheme { + GroupConversationOptionsItem( + title = "Custom", + arrowLabel = "12 weeks", + arrowType = ArrowType.TITLE_ALIGNED, + selected = true, + ) +} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModel.kt index a695794a2c6..f48501e1cd6 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModel.kt @@ -34,6 +34,7 @@ import com.wire.android.ui.home.conversationslist.model.Membership import com.wire.android.ui.home.newconversation.channelaccess.ChannelAccessType import com.wire.android.ui.home.newconversation.channelaccess.ChannelAddPermissionType import com.wire.android.ui.home.newconversation.channelaccess.toDomainEnum +import com.wire.android.ui.home.newconversation.channelhistory.ChannelHistoryType import com.wire.android.ui.home.newconversation.common.CreateGroupState import com.wire.android.ui.home.newconversation.groupOptions.GroupOptionState import com.wire.android.ui.home.newconversation.model.Contact @@ -87,6 +88,7 @@ class NewConversationViewModel @Inject constructor( } ) var isChannelCreationPossible: Boolean by mutableStateOf(true) + var isFreemiumAccount: Boolean by mutableStateOf(false) // TODO: implement logic to determine if the account is freemium var createGroupState: CreateGroupState by mutableStateOf(CreateGroupState.Default) @@ -139,6 +141,10 @@ class NewConversationViewModel @Inject constructor( newGroupState = newGroupState.copy(isChannel = isChannel) } + fun setChannelHistoryType(channelHistoryType: ChannelHistoryType) { + newGroupState = newGroupState.copy(channelHistoryType = channelHistoryType) + } + private fun setConversationCreationParam() { viewModelScope.launch { val selfUser = getSelfUser() @@ -265,6 +271,7 @@ class NewConversationViewModel @Inject constructor( ), access = Conversation.accessFor(groupOptionsState.isAllowGuestEnabled), channelAddPermission = newGroupState.channelAddPermissionType.toDomainEnum() + // TODO: include channel history type ) ) handleNewGroupCreationResult(result) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/AccessItem.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/AccessItem.kt index 7235ccc5a01..dfd5f122b55 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/AccessItem.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/AccessItem.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dimensions +import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireTypography import com.wire.android.util.ui.PreviewMultipleThemes @@ -71,7 +72,7 @@ fun AccessScreenItem( enabled = isEnabled ) Text( - text = stringResource(channelAccessType.label), + text = stringResource(channelAccessType.labelResId), style = MaterialTheme.wireTypography.body01, color = if (isEnabled) colorsScheme().onBackground else Color.Gray, modifier = Modifier.padding(vertical = dimensions().spacing16x) @@ -81,7 +82,7 @@ fun AccessScreenItem( @Composable @PreviewMultipleThemes -fun PreviewAccessScreenItem() { +fun PreviewAccessScreenItem() = WireTheme { AccessScreenItem( channelAccessType = ChannelAccessType.PRIVATE, selectedAccess = ChannelAccessType.PRIVATE, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/ChannelAccessType.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/ChannelAccessType.kt index 1f5a26ff9cf..76cd034b375 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/ChannelAccessType.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/ChannelAccessType.kt @@ -18,12 +18,13 @@ package com.wire.android.ui.home.newconversation.channelaccess import android.os.Parcelable +import androidx.annotation.StringRes import com.wire.android.R import com.wire.kalium.logic.data.conversation.ConversationDetails.Group.Channel.ChannelAccess import kotlinx.parcelize.Parcelize @Parcelize -enum class ChannelAccessType(val label: Int) : Parcelable { +enum class ChannelAccessType(@StringRes val labelResId: Int) : Parcelable { PUBLIC(R.string.channel_public_label), PRIVATE(R.string.channel_private_label) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/ChannelAddPermissionType.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/ChannelAddPermissionType.kt index 4046f96271a..88a93fb53a4 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/ChannelAddPermissionType.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/ChannelAddPermissionType.kt @@ -17,10 +17,11 @@ */ package com.wire.android.ui.home.newconversation.channelaccess +import androidx.annotation.StringRes import com.wire.android.R import com.wire.kalium.logic.data.conversation.ConversationDetails.Group.Channel.ChannelAddPermission -enum class ChannelAddPermissionType(val label: Int) { +enum class ChannelAddPermissionType(@StringRes val labelResId: Int) { ADMINS(R.string.channel_add_permission_admin_label), EVERYONE(R.string.channel_add_permission_admin_members_label) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/PermissionItem.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/PermissionItem.kt index 3986bcd54e1..0cc3988581f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/PermissionItem.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelaccess/PermissionItem.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.selectableBackground +import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireTypography import com.wire.android.util.ui.PreviewMultipleThemes @@ -53,7 +54,7 @@ fun PermissionItem( ) { RadioButton(selected = isSelected, onClick = { onItemClicked(channelAddPermissionType) }) Text( - text = stringResource(channelAddPermissionType.label), + text = stringResource(channelAddPermissionType.labelResId), style = MaterialTheme.wireTypography.body01, color = MaterialTheme.wireColorScheme.onBackground, modifier = Modifier.padding(vertical = dimensions().spacing16x) @@ -63,7 +64,7 @@ fun PermissionItem( @Composable @PreviewMultipleThemes -fun PreviewPermissionItem() { +fun PreviewPermissionItem() = WireTheme { PermissionItem( channelAddPermissionType = ChannelAddPermissionType.ADMINS, selectedPermission = ChannelAddPermissionType.ADMINS, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryCustomArgs.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryCustomArgs.kt new file mode 100644 index 00000000000..63f67f6a8a9 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryCustomArgs.kt @@ -0,0 +1,27 @@ +/* + * Wire + * Copyright (C) 2025 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.ui.home.newconversation.channelhistory + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +class ChannelHistoryCustomArgs(val currentType: ChannelHistoryType) : Parcelable + +@Parcelize +class ChannelHistoryCustomNavBackArgs(val customType: ChannelHistoryType.On.Specific) : Parcelable 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 new file mode 100644 index 00000000000..398bf59aa6d --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryCustomScreen.kt @@ -0,0 +1,155 @@ +/* + * Wire + * Copyright (C) 2025 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.ui.home.newconversation.channelhistory + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +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.rememberTextFieldState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource +import com.ramcosta.composedestinations.result.ResultBackNavigator +import com.wire.android.R +import com.wire.android.navigation.annotation.app.WireDestination +import com.wire.android.navigation.style.SlideNavigationAnimation +import com.wire.android.ui.common.WireDropDown +import com.wire.android.ui.common.dimensions +import com.wire.android.ui.common.scaffold.WireScaffold +import com.wire.android.ui.common.textfield.DefaultCode +import com.wire.android.ui.common.textfield.WireTextField +import com.wire.android.ui.common.textfield.maxLengthDigits +import com.wire.android.ui.common.topappbar.NavigationIconType +import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar +import com.wire.android.ui.home.newconversation.common.NewConversationNavGraph +import com.wire.android.ui.theme.WireTheme +import com.wire.android.util.ui.PreviewMultipleThemes + +@NewConversationNavGraph +@WireDestination( + navArgsDelegate = ChannelHistoryCustomArgs::class, + style = SlideNavigationAnimation::class, +) +@Composable +fun ChannelHistoryCustomScreen( + navArgs: ChannelHistoryCustomArgs, + resultNavigator: ResultBackNavigator, + modifier: Modifier = Modifier, +) { + val specificCurrentType = navArgs.currentType as? ChannelHistoryType.On.Specific + val amountState = rememberTextFieldState(specificCurrentType?.amount?.toString().orEmpty()) + var timeState by rememberSaveable { + mutableStateOf(specificCurrentType?.type ?: ChannelHistoryType.On.Specific.AmountType.Days) + } + + fun navigateBack() { + amountState.text.toString().toIntOrNull()?.takeIf { it > 0 }?.let { + resultNavigator.setResult(ChannelHistoryCustomNavBackArgs(ChannelHistoryType.On.Specific(it, timeState))) + } + resultNavigator.navigateBack() + } + + BackHandler(enabled = true, onBack = ::navigateBack) + + ChannelHistoryCustomScreenContent( + amountState = amountState, + typeState = timeState, + onTypeSelected = { timeState = it }, + onBackPressed = ::navigateBack, + modifier = modifier, + ) +} + +@Composable +fun ChannelHistoryCustomScreenContent( + amountState: TextFieldState, + typeState: ChannelHistoryType.On.Specific.AmountType, + onTypeSelected: (ChannelHistoryType.On.Specific.AmountType) -> Unit, + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, +) { + WireScaffold( + modifier = modifier, + topBar = { + WireCenterAlignedTopAppBar( + onNavigationPressed = onBackPressed, + elevation = dimensions().spacing0x, + title = stringResource(id = R.string.channel_history_custom), + titleContentDescription = stringResource(id = R.string.content_description_new_conversation_history_custom_heading), + navigationIconType = NavigationIconType.Back(R.string.content_description_new_conversation_history_back_btn) + ) + } + ) { internalPadding -> + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(dimensions().spacing8x), + modifier = Modifier + .padding(internalPadding) + .fillMaxWidth() + .padding(horizontal = dimensions().spacing16x) + ) { + val keyboardController = LocalSoftwareKeyboardController.current + WireTextField( + textState = amountState, + labelText = stringResource(R.string.channel_history_custom_amount_label), + keyboardOptions = KeyboardOptions.Companion.DefaultCode, + inputTransformation = InputTransformation.maxLengthDigits(MAX_AMOUNT_LENGTH), + onKeyboardAction = { keyboardController?.hide() }, + modifier = Modifier.weight(weight = 1f, fill = true), + ) + val items = ChannelHistoryType.On.Specific.AmountType.entries + val itemsNames = items.map { pluralStringResource(it.nameResId, amountState.text.toString().toIntOrNull() ?: 0) } + WireDropDown( + items = itemsNames, + defaultItemIndex = 0, + selectedItemIndex = items.indexOf(typeState), + label = stringResource(R.string.channel_history_custom_time_label), + autoUpdateSelection = true, + showDefaultTextIndicator = false, + onChangeClickDescription = stringResource(R.string.content_description_new_conversation_history_custom_change_time), + onSelected = { onTypeSelected(items[it]) }, + modifier = Modifier.weight(weight = 3f, fill = true) + ) + } + } +} + +private const val MAX_AMOUNT_LENGTH = 2 + +@PreviewMultipleThemes +@Composable +fun PreviewChannelHistoryCustomScreen() = WireTheme { + ChannelHistoryCustomScreenContent( + amountState = rememberTextFieldState("1"), + typeState = ChannelHistoryType.On.Specific.AmountType.Days, + onTypeSelected = {}, + onBackPressed = {}, + ) +} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryScreen.kt new file mode 100644 index 00000000000..883af6df06e --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryScreen.kt @@ -0,0 +1,199 @@ +/* + * Wire + * Copyright (C) 2025 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.ui.home.newconversation.channelhistory + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import com.ramcosta.composedestinations.result.NavResult +import com.ramcosta.composedestinations.result.ResultRecipient +import com.wire.android.R +import com.wire.android.model.Clickable +import com.wire.android.navigation.NavigationCommand +import com.wire.android.navigation.WireNavigator +import com.wire.android.navigation.annotation.app.WireDestination +import com.wire.android.navigation.style.SlideNavigationAnimation +import com.wire.android.ui.common.WirePromotionCard +import com.wire.android.ui.common.colorsScheme +import com.wire.android.ui.common.dimensions +import com.wire.android.ui.common.scaffold.WireScaffold +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.destinations.ChannelHistoryCustomScreenDestination +import com.wire.android.ui.home.conversations.details.options.ArrowType +import com.wire.android.ui.home.conversations.details.options.GroupConversationOptionsItem +import com.wire.android.ui.home.newconversation.NewConversationViewModel +import com.wire.android.ui.home.newconversation.common.NewConversationNavGraph +import com.wire.android.ui.theme.WireTheme +import com.wire.android.util.ui.PreviewMultipleThemes + +@NewConversationNavGraph +@WireDestination( + style = SlideNavigationAnimation::class, +) +@Composable +fun ChannelHistoryScreen( + navigator: WireNavigator, + customResultRecipient: ResultRecipient, + newConversationViewModel: NewConversationViewModel, + modifier: Modifier = Modifier, +) { + customResultRecipient.onNavResult { result -> + when (result) { + is NavResult.Canceled -> {} + is NavResult.Value -> newConversationViewModel.setChannelHistoryType(result.value.customType) + } + } + + ChannelHistoryScreenContent( + selectedHistoryOption = newConversationViewModel.newGroupState.channelHistoryType, + onHistoryOptionSelected = newConversationViewModel::setChannelHistoryType, + onOpenCustomChooser = { + val navArgs = ChannelHistoryCustomArgs(newConversationViewModel.newGroupState.channelHistoryType) + navigator.navigate(NavigationCommand(ChannelHistoryCustomScreenDestination(navArgs))) + }, + onBackPressed = navigator::navigateBack, + onUpgradeNowClicked = { /* TODO: Implement upgrade action */ }, + modifier = modifier, + ) +} + +@Suppress("CyclomaticComplexMethod") +@Composable +fun ChannelHistoryScreenContent( + selectedHistoryOption: ChannelHistoryType, + onHistoryOptionSelected: (ChannelHistoryType) -> Unit, + onOpenCustomChooser: () -> Unit, + onBackPressed: () -> Unit, + onUpgradeNowClicked: () -> Unit, + modifier: Modifier = Modifier, + isFreemiumAccount: Boolean = false +) { + WireScaffold( + modifier = modifier, + topBar = { + WireCenterAlignedTopAppBar( + onNavigationPressed = onBackPressed, + elevation = dimensions().spacing0x, + title = stringResource(id = R.string.channel_history_label), + titleContentDescription = stringResource(id = R.string.content_description_new_conversation_history_heading), + navigationIconType = NavigationIconType.Back(R.string.content_description_new_group_options_back_btn) + ) + } + ) { internalPadding -> + LazyColumn(modifier = Modifier.padding(internalPadding)) { + val items = when (isFreemiumAccount) { + true -> defaultFreemiumHistoryTypes + false -> defaultHistoryTypes.plus( + when { + // add the chosen custom option if it is selected + selectedHistoryOption.isCustom() -> selectedHistoryOption + // otherwise add a placeholder for custom option + else -> ChannelHistoryType.On.Specific(0, ChannelHistoryType.On.Specific.AmountType.Days) + } + ) + } + + items(count = items.size) { index -> + val item = items[index] + val isSelected = item == selectedHistoryOption + GroupConversationOptionsItem( + title = item.name(useAmountForCustom = false), + arrowLabel = when { + isSelected && item is ChannelHistoryType.On.Specific && item.isCustom() -> item.amountAsString() + else -> null + }, + arrowType = when { + item.isCustom() -> ArrowType.CENTER_ALIGNED + else -> ArrowType.NONE + }, + selected = selectedHistoryOption == item, + clickable = Clickable { + when { + item is ChannelHistoryType.On.Specific && item.isCustom() -> onOpenCustomChooser() + else -> onHistoryOptionSelected(item) + } + }, + ) + } + item { + Text( // footer description text + text = stringResource(id = R.string.channel_history_description), + style = typography().body01, + color = colorsScheme().secondaryText, + modifier = Modifier.padding( + top = dimensions().spacing4x, + bottom = dimensions().spacing16x, + start = dimensions().spacing16x, + end = dimensions().spacing16x, + ) + ) + } + if (isFreemiumAccount) { + item { + ChannelHistoryFreemiumUpgradeCard( + onUpgradeNowClicked = onUpgradeNowClicked, + ) + } + } + } + } +} + +@Composable +private fun ChannelHistoryFreemiumUpgradeCard( + onUpgradeNowClicked: () -> Unit, +) { + WirePromotionCard( + title = stringResource(id = R.string.channel_history_freemium_upgrade_title), + description = stringResource(id = R.string.channel_history_freemium_upgrade_description), + buttonLabel = stringResource(id = R.string.channel_history_freemium_upgrade_now), + onButtonClick = onUpgradeNowClicked, + modifier = Modifier.padding(horizontal = dimensions().spacing16x, vertical = dimensions().spacing8x), + ) +} + +@PreviewMultipleThemes +@Composable +fun PreviewChannelHistoryScreenPremium() = WireTheme { + ChannelHistoryScreenContent( + isFreemiumAccount = false, + selectedHistoryOption = ChannelHistoryType.On.Specific(2, ChannelHistoryType.On.Specific.AmountType.Weeks), + onHistoryOptionSelected = {}, + onOpenCustomChooser = {}, + onUpgradeNowClicked = {}, + onBackPressed = {}, + ) +} + +@PreviewMultipleThemes +@Composable +fun PreviewChannelHistoryScreenFreemium() = WireTheme { + ChannelHistoryScreenContent( + isFreemiumAccount = true, + selectedHistoryOption = ChannelHistoryType.On.Specific(1, ChannelHistoryType.On.Specific.AmountType.Days), + onHistoryOptionSelected = {}, + onOpenCustomChooser = {}, + onUpgradeNowClicked = {}, + onBackPressed = {}, + ) +} diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryType.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryType.kt new file mode 100644 index 00000000000..ef20c1b7eef --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/channelhistory/ChannelHistoryType.kt @@ -0,0 +1,79 @@ +/* + * Wire + * Copyright (C) 2025 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.ui.home.newconversation.channelhistory + +import android.os.Parcelable +import androidx.annotation.PluralsRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource +import com.wire.android.R +import kotlinx.parcelize.Parcelize + +sealed interface ChannelHistoryType : Parcelable { + + @Parcelize + data object Off : ChannelHistoryType + + sealed interface On : ChannelHistoryType { + + @Parcelize + data object Unlimited : On + + @Parcelize + data class Specific(val amount: Int, val type: AmountType) : On { + + @Parcelize + enum class AmountType(@PluralsRes val nameResId: Int, @PluralsRes val nameWithAmountResId: Int) : Parcelable { + Days(R.plurals.days_label, R.plurals.days_long_label), + Weeks(R.plurals.weeks_label, R.plurals.weeks_long_label), + Months(R.plurals.months_label, R.plurals.months_long_label) + } + } + } +} + +@Suppress("MagicNumber") +val defaultHistoryTypes: List = listOf( + ChannelHistoryType.Off, + ChannelHistoryType.On.Specific(1, ChannelHistoryType.On.Specific.AmountType.Days), + ChannelHistoryType.On.Specific(1, ChannelHistoryType.On.Specific.AmountType.Weeks), + ChannelHistoryType.On.Specific(4, ChannelHistoryType.On.Specific.AmountType.Weeks), + ChannelHistoryType.On.Unlimited, +) + +@Suppress("MagicNumber") +val defaultFreemiumHistoryTypes: List = listOf( + ChannelHistoryType.Off, + ChannelHistoryType.On.Specific(1, ChannelHistoryType.On.Specific.AmountType.Days), +) + +fun ChannelHistoryType.isCustom(): Boolean = !defaultHistoryTypes.contains(this) + +@Composable +fun ChannelHistoryType.name(useAmountForCustom: Boolean): String = when (this) { + is ChannelHistoryType.Off -> stringResource(R.string.channel_history_off) + is ChannelHistoryType.On.Unlimited -> stringResource(R.string.channel_history_unlimited) + is ChannelHistoryType.On.Specific -> when { + this.isCustom() && !useAmountForCustom -> stringResource(R.string.channel_history_custom) + else -> this.amountAsString() + } +} + +@Composable +fun ChannelHistoryType.On.Specific.amountAsString(): String = pluralStringResource(type.nameWithAmountResId, amount, amount) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/groupOptions/GroupOptionsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/groupOptions/GroupOptionsScreen.kt index 6b71f85b914..c8c7e3f7561 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/groupOptions/GroupOptionsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/groupOptions/GroupOptionsScreen.kt @@ -21,10 +21,10 @@ package com.wire.android.ui.home.newconversation.groupOptions import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -32,7 +32,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview +import com.wire.android.BuildConfig import com.wire.android.R import com.wire.android.model.Clickable import com.wire.android.navigation.BackStackMode @@ -44,24 +44,32 @@ import com.wire.android.ui.common.WireDialogButtonProperties import com.wire.android.ui.common.WireDialogButtonType 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.dimensions +import com.wire.android.ui.common.groupname.GroupMetadataState import com.wire.android.ui.common.scaffold.WireScaffold 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.destinations.ChannelAccessOnCreateScreenDestination +import com.wire.android.ui.destinations.ChannelHistoryScreenDestination import com.wire.android.ui.destinations.ConversationScreenDestination import com.wire.android.ui.destinations.HomeScreenDestination import com.wire.android.ui.destinations.NewGroupConversationSearchPeopleScreenDestination import com.wire.android.ui.home.conversations.details.options.ArrowType import com.wire.android.ui.home.conversations.details.options.GroupConversationOptionsItem import com.wire.android.ui.home.newconversation.NewConversationViewModel +import com.wire.android.ui.home.newconversation.channelaccess.ChannelAccessType +import com.wire.android.ui.home.newconversation.channelhistory.ChannelHistoryType +import com.wire.android.ui.home.newconversation.channelhistory.name import com.wire.android.ui.home.newconversation.common.CreateGroupErrorDialog import com.wire.android.ui.home.newconversation.common.CreateGroupState import com.wire.android.ui.home.newconversation.common.NewConversationNavGraph import com.wire.android.ui.home.settings.SwitchState +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.util.ui.PreviewMultipleThemes import com.wire.kalium.logic.data.id.ConversationId @NewConversationNavGraph @@ -83,11 +91,13 @@ fun GroupOptionScreen( GroupOptionScreenContent( groupOptionState = newConversationViewModel.groupOptionsState, createGroupState = newConversationViewModel.createGroupState, - accessTypeLabel = newConversationViewModel.newGroupState.channelAccessType.label, - isChannelsAllowed = newConversationViewModel.newGroupState.isChannel, + groupMetadataState = newConversationViewModel.newGroupState, onAccessClicked = { navigator.navigate(NavigationCommand(ChannelAccessOnCreateScreenDestination)) }, + onHistoryClicked = { + navigator.navigate(NavigationCommand(ChannelHistoryScreenDestination)) + }, onAllowGuestChanged = newConversationViewModel::onAllowGuestStatusChanged, onAllowServicesChanged = newConversationViewModel::onAllowServicesStatusChanged, onReadReceiptChanged = newConversationViewModel::onReadReceiptStatusChanged, @@ -118,9 +128,9 @@ fun GroupOptionScreen( fun GroupOptionScreenContent( groupOptionState: GroupOptionState, createGroupState: CreateGroupState, - accessTypeLabel: Int, - isChannelsAllowed: Boolean, + groupMetadataState: GroupMetadataState, onAccessClicked: () -> Unit, + onHistoryClicked: () -> Unit, onAllowGuestChanged: ((Boolean) -> Unit), onAllowServicesChanged: ((Boolean) -> Unit), onReadReceiptChanged: ((Boolean) -> Unit), @@ -136,12 +146,12 @@ fun GroupOptionScreenContent( ) { with(groupOptionState) { WireScaffold(topBar = { - val screenTitle = if (isChannelsAllowed) { + val screenTitle = if (groupMetadataState.isChannel) { R.string.new_channel_title } else { R.string.new_group_title } - val navigationIconType = if (isChannelsAllowed) { + val navigationIconType = if (groupMetadataState.isChannel) { NavigationIconType.Back(R.string.content_description_new_channel_options_back_btn) } else { NavigationIconType.Back(R.string.content_description_new_group_options_back_btn) @@ -155,15 +165,18 @@ fun GroupOptionScreenContent( ) }) { internalPadding -> GroupOptionsScreenMainContent( - accessTypeLabel, - isChannelsAllowed, - internalPadding, + groupMetadataState, onAccessClicked, + onHistoryClicked, onAllowGuestChanged, onAllowServicesChanged, onReadReceiptChanged, onEnableWireCellChanged, - onContinuePressed + onContinuePressed, + Modifier + .fillMaxSize() + .padding(internalPadding) + .background(MaterialTheme.colorScheme.background) ) } @@ -178,35 +191,38 @@ fun GroupOptionScreenContent( @Composable private fun GroupOptionState.GroupOptionsScreenMainContent( - accessTypeLabel: Int, - isChannel: Boolean, - internalPadding: PaddingValues, + groupMetadataState: GroupMetadataState, onAccessClicked: () -> Unit, + onHistoryClicked: () -> Unit, onAllowGuestChanged: (Boolean) -> Unit, onAllowServicesChanged: (Boolean) -> Unit, onReadReceiptChanged: (Boolean) -> Unit, onEnableWireCellChanged: (Boolean) -> Unit, - onContinuePressed: () -> Unit + onContinuePressed: () -> Unit, + modifier: Modifier = Modifier, ) { Column( - modifier = Modifier - .fillMaxSize() - .padding(internalPadding) - .background(MaterialTheme.colorScheme.background), + modifier = modifier, verticalArrangement = Arrangement.SpaceBetween ) { Column { - if (isChannel) { - AccessOptions(accessTypeLabel, onAccessClicked) + if (groupMetadataState.isChannel) { + AccessOptions(groupMetadataState.channelAccessType, onAccessClicked) + HorizontalDivider(color = colorsScheme().divider) + if (BuildConfig.CHANNELS_HISTORY_OPTIONS_ENABLED) { + HistoryOptions(groupMetadataState.channelHistoryType, onHistoryClicked) + HorizontalDivider(color = colorsScheme().divider) + } } - AllowGuestsOptions(isChannel, onAllowGuestChanged) - AllowServicesOptions(isChannel, onAllowServicesChanged) - ReadReceiptsOptions(isChannel, onReadReceiptChanged) + AllowGuestsOptions(groupMetadataState.isChannel, onAllowGuestChanged) + HorizontalDivider(color = colorsScheme().divider) + AllowServicesOptions(groupMetadataState.isChannel, onAllowServicesChanged) + ReadReceiptsOptions(groupMetadataState.isChannel, onReadReceiptChanged) isWireCellsEnabled?.let { EnableWireCellOptions(onEnableWireCellChanged) } } - CreateGroupButton(isChannel, onContinuePressed) + CreateGroupButton(groupMetadataState.isChannel, onContinuePressed) } } @@ -269,15 +285,27 @@ private fun GroupOptionState.AllowServicesOptions(isChannel: Boolean, onAllowSer @Composable fun AccessOptions( - accessTypeLabel: Int, + accessType: ChannelAccessType, onAccessClicked: () -> Unit ) { GroupConversationOptionsItem( title = stringResource(R.string.channel_access_label), arrowType = ArrowType.TITLE_ALIGNED, - arrowLabel = stringResource(accessTypeLabel), - onClick = onAccessClicked, - isClickable = true, + arrowLabel = stringResource(accessType.labelResId), + clickable = Clickable(enabled = true, onClick = onAccessClicked), + ) +} + +@Composable +fun HistoryOptions( + historyType: ChannelHistoryType, + onClicked: () -> Unit +) { + GroupConversationOptionsItem( + title = stringResource(R.string.channel_history_label), + arrowType = ArrowType.TITLE_ALIGNED, + arrowLabel = historyType.name(useAmountForCustom = true), + clickable = Clickable(enabled = true, onClick = onClicked), ) } @@ -376,13 +404,25 @@ private fun AllowGuestsDialog( } @Composable -@Preview -fun PreviewGroupOptionScreen() { +@PreviewMultipleThemes +fun PreviewGroupOptionScreen() = WireTheme { GroupOptionScreenContent( - GroupOptionState(), - CreateGroupState.Default, - accessTypeLabel = R.string.channel_private_label, - isChannelsAllowed = false, - {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} + groupOptionState = GroupOptionState(), + createGroupState = CreateGroupState.Default, + groupMetadataState = GroupMetadataState(isChannel = true), + onAccessClicked = {}, + onHistoryClicked = {}, + onAllowGuestChanged = {}, + onAllowServicesChanged = {}, + onReadReceiptChanged = {}, + onEnableWireCellChanged = {}, + onContinuePressed = {}, + onAllowGuestsDialogDismissed = {}, + onNotAllowGuestsClicked = {}, + onAllowGuestsClicked = {}, + onErrorDismissed = {}, + onEditParticipantsClick = {}, + onDiscardGroupCreationClick = {}, + onBackPressed = {} ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/search/ChannelNotAvailableDialog.kt b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/search/ChannelNotAvailableDialog.kt index 5a12cadcda3..2288be81f08 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/newconversation/search/ChannelNotAvailableDialog.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/newconversation/search/ChannelNotAvailableDialog.kt @@ -17,11 +17,14 @@ */ package com.wire.android.ui.home.newconversation.search +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import com.wire.android.R import com.wire.android.ui.common.WirePromotionDialog +import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.preview.MultipleThemePreviews import com.wire.android.ui.theme.WireTheme @@ -36,15 +39,15 @@ fun ChannelNotAvailableDialog( description = stringResource(R.string.channel_not_available_dialog_description), buttonLabel = stringResource(R.string.channel_not_available_dialog_create_team_button), onDismiss = onDismiss, - onCreateTeam = onCreateTeam, + onButtonClick = onCreateTeam, modifier = modifier, ) } @Composable @MultipleThemePreviews -fun PreviewWireDialogWithWaves() { - WireTheme { +fun PreviewWireDialogWithWaves() = WireTheme { + Box(modifier = Modifier.padding(dimensions().dialogCardMargin)) { ChannelNotAvailableDialog( onDismiss = {}, onCreateTeam = {}, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 86a9fbebd21..69df8800bf6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -237,6 +237,10 @@ Type group name Type channel name Conversation options + Conversation history options + Conversation history custom + Go back to conversation history options + Change time option Go back to new group creation Go back to new channel creation pending approval of connection request @@ -562,6 +566,19 @@ Select who can add participants to a private channel Admins Admins and members + Conversation history + Select a period. When participants join this channel, they can follow the history for this time frame. + Show older messages? + Upgrade to a paid plan to offer channel members the whole history. + Upgrade now + Off + Unlimited + Custom + days + weeks + months + AMOUNT + TIME Create Group Create Channel Up to 500 people can join a group conversation. @@ -1537,6 +1554,10 @@ In group conversations, the group admin can overwrite this setting. 1 week %1$d weeks + + 1 month + %1$d months + 1 day %1$d days @@ -1553,6 +1574,18 @@ In group conversations, the group admin can overwrite this setting. 1 second %1$d seconds + + day + days + + + week + weeks + + + month + months + 4w 1w 1d diff --git a/buildSrc/src/main/kotlin/customization/FeatureConfigs.kt b/buildSrc/src/main/kotlin/customization/FeatureConfigs.kt index 0ec7998a7c4..e30ae5d7702 100644 --- a/buildSrc/src/main/kotlin/customization/FeatureConfigs.kt +++ b/buildSrc/src/main/kotlin/customization/FeatureConfigs.kt @@ -109,6 +109,7 @@ enum class FeatureConfigs(val value: String, val configType: ConfigType) { PAGINATED_CONVERSATION_LIST_ENABLED("paginated_conversation_list_enabled", ConfigType.BOOLEAN), PUBLIC_CHANNELS_ENABLED("public_channels_enabled", ConfigType.BOOLEAN), + CHANNELS_HISTORY_OPTIONS_ENABLED("channels_history_options_enabled", ConfigType.BOOLEAN), USE_NEW_LOGIN_FOR_DEFAULT_BACKEND("use_new_login_for_default_backend", ConfigType.BOOLEAN), /** diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WirePromotionDialog.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WirePromotionDialog.kt index 845d2ffb9ca..628d2d33db7 100644 --- a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WirePromotionDialog.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/WirePromotionDialog.kt @@ -2,8 +2,11 @@ package com.wire.android.ui.common import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -14,18 +17,16 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.wire.android.ui.common.button.WireSecondaryButton -import com.wire.android.ui.common.button.wireSecondaryButtonColors import com.wire.android.ui.common.preview.MultipleThemePreviews -import com.wire.android.ui.theme.wireColorScheme +import com.wire.android.ui.theme.WireColorScheme +import com.wire.android.ui.theme.WireTheme import com.wire.android.ui.theme.wireDimensions -import com.wire.android.ui.theme.wireTypography @Composable fun WirePromotionDialog( @@ -33,74 +34,88 @@ fun WirePromotionDialog( description: String, buttonLabel: String, onDismiss: () -> Unit, - onCreateTeam: () -> Unit, + onButtonClick: () -> Unit, modifier: Modifier = Modifier, properties: DialogProperties = wireDialogPropertiesBuilder(), shape: Shape = RoundedCornerShape(MaterialTheme.wireDimensions.dialogCornerSize), contentPadding: PaddingValues = PaddingValues(MaterialTheme.wireDimensions.dialogContentPadding) ) { - Dialog( onDismissRequest = onDismiss, properties = properties ) { + WirePromotionCard( + title = title, + description = description, + buttonLabel = buttonLabel, + onButtonClick = onButtonClick, + modifier = modifier, + shape = shape, + contentPadding = contentPadding + ) + } +} + +@Composable +fun WirePromotionCard( + title: String, + description: String, + buttonLabel: String, + onButtonClick: () -> Unit, + modifier: Modifier = Modifier, + shape: Shape = RoundedCornerShape(MaterialTheme.wireDimensions.dialogCornerSize), + contentPadding: PaddingValues = PaddingValues(MaterialTheme.wireDimensions.dialogContentPadding) +) { + WireColorScheme(darkColorsScheme()) { Surface( - modifier = modifier - .fillMaxWidth() - .height(220.dp) - .padding(MaterialTheme.wireDimensions.dialogCardMargin), + modifier = modifier.fillMaxWidth(), shape = shape, - border = BorderStroke(1.dp, colorsScheme().secondaryButtonEnabledPromotion), - color = Color.Black, - contentColor = MaterialTheme.colorScheme.onSurface, + border = BorderStroke(dimensions().spacing1x, colorsScheme().outline), ) { - Image( - modifier = Modifier.fillMaxWidth(), - painter = painterResource(id = R.drawable.ic_wave), - contentDescription = null, - alignment = Alignment.TopEnd, - ) - Column( - modifier = Modifier - .fillMaxWidth() - .padding(contentPadding), - horizontalAlignment = Alignment.Start - ) { - Text( - text = title, - style = MaterialTheme.wireTypography.title02, - color = Color.White, - modifier = Modifier.padding( - end = dimensions().spacing64x, - bottom = MaterialTheme.wireDimensions.dialogTextsSpacing + Box(modifier = Modifier.height(IntrinsicSize.Min)) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(contentPadding), + horizontalAlignment = Alignment.Start + ) { + Text( + text = title, + style = typography().title02, + modifier = Modifier.padding( + end = dimensions().spacing64x, + bottom = dimensions().spacing8x + ) + ) + Text( + text = description, + style = typography().body01, + modifier = Modifier.padding( + end = dimensions().spacing100x, + bottom = dimensions().spacing16x + ), ) - ) - Text( - text = description, - style = MaterialTheme.wireTypography.body01, - color = Color.White, - modifier = Modifier.padding( - end = dimensions().spacing100x, - bottom = MaterialTheme.wireDimensions.dialogTextsSpacing + WireSecondaryButton( + text = buttonLabel, + onClick = onButtonClick, + fillMaxWidth = false, + minSize = dimensions().buttonSmallMinSize, + minClickableSize = dimensions().buttonSmallMinSize, ) - ) - WireSecondaryButton( - modifier = Modifier, - text = buttonLabel, - onClick = onCreateTeam, - fillMaxWidth = false, - colors = wireSecondaryButtonColors().copy( - onEnabled = colorsScheme().onSecondaryButtonEnabledPromotion, - enabled = colorsScheme().secondaryButtonEnabledPromotion, - enabledOutline = colorsScheme().secondaryButtonEnabledOutlinePromotion, - enabledRipple = colorsScheme().secondaryButtonRipplePromotion, - positive = MaterialTheme.wireColorScheme.secondaryButtonEnabledPromotion, - positiveOutline = MaterialTheme.wireColorScheme.secondaryButtonEnabledOutlinePromotion, - positiveRipple = MaterialTheme.wireColorScheme.secondaryButtonRipplePromotion, - ), - minSize = dimensions().buttonSmallMinSize, - minClickableSize = dimensions().buttonMinClickableSize, - ) + } + Box( + modifier = Modifier + .align(Alignment.CenterEnd) + .fillMaxSize() + ) { + Image( + painter = painterResource(id = R.drawable.bg_waves_promotion), + contentDescription = null, + alignment = Alignment.TopEnd, + contentScale = ContentScale.None, + modifier = Modifier.matchParentSize() + ) + } } } } @@ -108,12 +123,14 @@ fun WirePromotionDialog( @MultipleThemePreviews @Composable -fun PreviewWireDialogWithWaves() { - WirePromotionDialog( - title = "Title", - description = "Description", - buttonLabel = "Button", - onDismiss = {}, - onCreateTeam = {}, - ) +fun PreviewWireDialogWithWaves() = WireTheme { + Box(modifier = Modifier.padding(dimensions().dialogCardMargin)) { + WirePromotionDialog( + title = "Title", + description = "Description", + buttonLabel = "Button", + onDismiss = {}, + onButtonClick = {}, + ) + } } diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/Theme.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/Theme.kt index 3143e4e9886..7477b7d557e 100644 --- a/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/Theme.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/Theme.kt @@ -62,6 +62,22 @@ fun WireTheme( } } +@Composable +fun WireColorScheme( + wireColorScheme: WireColorScheme = WireColorSchemeTypes.currentTheme, + content: @Composable () -> Unit +) { + CompositionLocalProvider( + LocalWireColors provides wireColorScheme + ) { + MaterialTheme( + colorScheme = wireColorScheme.toColorScheme() + ) { + content() + } + } +} + private val LocalWireColors = staticCompositionLocalOf { WireColorSchemeTypes.light } private val LocalWireFixedColors = staticCompositionLocalOf { DefaultWireFixedColorScheme } private val LocalWireTypography = staticCompositionLocalOf { WireTypographyTypes.defaultPhone } diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt index d26bf682176..9b35a4ae651 100644 --- a/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt @@ -80,10 +80,6 @@ class WireColorScheme( val tertiaryButtonSelected: Color, val onTertiaryButtonSelected: Color, val tertiaryButtonSelectedOutline: Color, val tertiaryButtonRipple: Color, - val secondaryButtonEnabledPromotion: Color, - val onSecondaryButtonEnabledPromotion: Color, - val secondaryButtonEnabledOutlinePromotion: Color, - val secondaryButtonRipplePromotion: Color, // strokes and shadows val outline: Color, @@ -171,10 +167,6 @@ private val LightWireColorScheme = WireColorScheme( tertiaryButtonSelected = WireColorPalette.LightBlue50, onTertiaryButtonSelected = WireColorPalette.LightBlue500, tertiaryButtonSelectedOutline = WireColorPalette.LightBlue300, tertiaryButtonRipple = Color.Black, - secondaryButtonEnabledPromotion = WireColorPalette.Gray90, - onSecondaryButtonEnabledPromotion = Color.White, - secondaryButtonEnabledOutlinePromotion = WireColorPalette.Gray100, - secondaryButtonRipplePromotion = Color.White, // strokes and shadows outline = WireColorPalette.Gray40, @@ -268,10 +260,6 @@ private val DarkWireColorScheme = WireColorScheme( tertiaryButtonSelected = WireColorPalette.DarkBlue800, onTertiaryButtonSelected = WireColorPalette.DarkBlue500, tertiaryButtonSelectedOutline = WireColorPalette.DarkBlue800, tertiaryButtonRipple = Color.White, - secondaryButtonEnabledPromotion = WireColorPalette.Gray90, - onSecondaryButtonEnabledPromotion = Color.White, - secondaryButtonEnabledOutlinePromotion = WireColorPalette.Gray100, - secondaryButtonRipplePromotion = Color.White, // strokes and shadows outline = WireColorPalette.Gray90, diff --git a/core/ui-common/src/main/res/drawable/ic_wave.xml b/core/ui-common/src/main/res/drawable/bg_waves_promotion.xml similarity index 54% rename from core/ui-common/src/main/res/drawable/ic_wave.xml rename to core/ui-common/src/main/res/drawable/bg_waves_promotion.xml index 4edd0a5fd55..e300b3456ab 100644 --- a/core/ui-common/src/main/res/drawable/ic_wave.xml +++ b/core/ui-common/src/main/res/drawable/bg_waves_promotion.xml @@ -1,4 +1,4 @@ - + android:width="390dp" + android:height="428dp" + android:autoMirrored="true" + android:viewportWidth="390" + android:viewportHeight="428"> + + + + + + + + diff --git a/default.json b/default.json index 7d9bd612453..b8a6ba988a1 100644 --- a/default.json +++ b/default.json @@ -33,7 +33,8 @@ "analytics_enabled": false, "analytics_app_key": "8ffae535f1836ed5f58fd5c8a11c00eca07c5438", "analytics_server_url": "https://countly.wire.com/", - "enable_new_registration": true + "enable_new_registration": true, + "channels_history_options_enabled": true }, "staging": { "application_id": "com.waz.zclient.dev", @@ -141,6 +142,7 @@ "paginated_conversation_list_enabled": false, "should_display_release_notes": true, "public_channels_enabled": false, + "channels_history_options_enabled": false, "use_new_login_for_default_backend": true, "enable_crossplatform_backup": true }