Skip to content

Commit 128d7c1

Browse files
authored
feat: secure public link ui update (WPB-20914) (#4426)
1 parent b7c3673 commit 128d7c1

File tree

9 files changed

+516
-99
lines changed

9 files changed

+516
-99
lines changed

app/src/main/kotlin/com/wire/android/navigation/WireMainNavGraph.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import com.wire.android.feature.cells.ui.destinations.ConversationFilesScreenDes
2525
import com.wire.android.feature.cells.ui.destinations.ConversationFilesWithSlideInTransitionScreenDestination
2626
import com.wire.android.feature.cells.ui.destinations.CreateFolderScreenDestination
2727
import com.wire.android.feature.cells.ui.destinations.MoveToFolderScreenDestination
28+
import com.wire.android.feature.cells.ui.destinations.PublicLinkExpirationScreenDestination
29+
import com.wire.android.feature.cells.ui.destinations.PublicLinkPasswordScreenDestination
2830
import com.wire.android.feature.cells.ui.destinations.PublicLinkScreenDestination
2931
import com.wire.android.feature.cells.ui.destinations.RecycleBinScreenDestination
3032
import com.wire.android.feature.cells.ui.destinations.RenameNodeScreenDestination
@@ -44,6 +46,8 @@ object WireMainNavGraph : NavGraphSpec {
4446
.plus(RecycleBinScreenDestination)
4547
.plus(RenameNodeScreenDestination)
4648
.plus(AddRemoveTagsScreenDestination)
49+
.plus(PublicLinkPasswordScreenDestination)
50+
.plus(PublicLinkExpirationScreenDestination)
4751
override val destinationsByRoute = destinations.associateBy { it.route }
4852
override val nestedNavGraphs = NavGraphs.wireRoot.nestedNavGraphs
4953
}

features/cells/src/main/java/com/wire/android/feature/cells/ui/publiclink/PublicLinkScreen.kt

Lines changed: 61 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,20 @@ import androidx.compose.ui.platform.LocalContext
4141
import androidx.compose.ui.res.stringResource
4242
import androidx.compose.ui.text.AnnotatedString
4343
import androidx.hilt.navigation.compose.hiltViewModel
44+
import com.ramcosta.composedestinations.result.NavResult
4445
import com.ramcosta.composedestinations.result.ResultBackNavigator
46+
import com.ramcosta.composedestinations.result.ResultRecipient
47+
import com.ramcosta.composedestinations.spec.DestinationSpec
4548
import com.wire.android.feature.cells.R
49+
import com.wire.android.feature.cells.ui.destinations.PublicLinkExpirationScreenDestination
50+
import com.wire.android.feature.cells.ui.destinations.PublicLinkPasswordScreenDestination
51+
import com.wire.android.feature.cells.ui.publiclink.settings.PublicLinkSettingsSection
4652
import com.wire.android.feature.cells.ui.util.PreviewMultipleThemes
53+
import com.wire.android.navigation.NavigationCommand
54+
import com.wire.android.navigation.WireNavigator
4755
import com.wire.android.navigation.annotation.features.cells.WireDestination
4856
import com.wire.android.navigation.style.PopUpNavigationAnimation
4957
import com.wire.android.ui.common.HandleActions
50-
import com.wire.android.ui.common.button.WireSecondaryButton
5158
import com.wire.android.ui.common.button.WireSwitch
5259
import com.wire.android.ui.common.colorsScheme
5360
import com.wire.android.ui.common.dimensions
@@ -63,7 +70,10 @@ import com.wire.android.ui.theme.WireTheme
6370
)
6471
@Composable
6572
fun PublicLinkScreen(
73+
navigator: WireNavigator,
6674
resultNavigator: ResultBackNavigator<Unit>,
75+
onPasswordChange: ResultRecipient<PublicLinkPasswordScreenDestination, Boolean>,
76+
onExpirationChange: ResultRecipient<PublicLinkExpirationScreenDestination, Boolean>,
6777
modifier: Modifier = Modifier,
6878
viewModel: PublicLinkViewModel = hiltViewModel(),
6979
) {
@@ -77,7 +87,7 @@ fun PublicLinkScreen(
7787
topBar = {
7888
WireCenterAlignedTopAppBar(
7989
onNavigationPressed = { resultNavigator.navigateBack() },
80-
title = if (viewModel.isFolder()) {
90+
title = if (state.isFolder) {
8191
stringResource(R.string.share_folder_via_link)
8292
} else {
8393
stringResource(R.string.share_file_via_link)
@@ -91,32 +101,37 @@ fun PublicLinkScreen(
91101
modifier = Modifier.padding(innerPadding)
92102
) {
93103
EnableLinkSection(
94-
checked = state.enabled,
95-
isFolder = viewModel.isFolder(),
104+
checked = state.isEnabled,
105+
isFolder = state.isFolder,
96106
onCheckChange = {
97107
viewModel.onEnabled(it)
98108
}
99109
)
100110

101111
AnimatedVisibility(
102-
visible = state.enabled,
112+
visible = state.isLinkAvailable,
103113
enter = fadeIn(),
104114
exit = fadeOut()
105115
) {
106-
PublicLinkSection(
107-
url = state.url,
108-
onShareLink = {
109-
state.url?.let { url ->
110-
viewModel.shareLink(url)
111-
}
112-
},
113-
onCopyLink = {
114-
state.url?.let { url ->
115-
clipboardManager.setText(AnnotatedString(url))
116-
showLinkCopiedToast(context)
117-
}
116+
Column {
117+
state.settings?.let {
118+
PublicLinkSettingsSection(
119+
settings = it,
120+
onPasswordClick = {
121+
navigator.navigate(NavigationCommand(PublicLinkPasswordScreenDestination()))
122+
},
123+
onExpirationClick = {
124+
navigator.navigate(NavigationCommand(PublicLinkExpirationScreenDestination()))
125+
},
126+
)
118127
}
119-
)
128+
129+
PublicLinkSection(
130+
state = state.linkState,
131+
onShareLink = viewModel::shareLink,
132+
onCopyLink = viewModel::copyLink,
133+
)
134+
}
120135
}
121136
}
122137
}
@@ -131,6 +146,23 @@ fun PublicLinkScreen(
131146
resultNavigator.navigateBack()
132147
}
133148
}
149+
150+
is CopyLink -> {
151+
clipboardManager.setText(AnnotatedString(action.url))
152+
showLinkCopiedToast(context)
153+
}
154+
}
155+
}
156+
157+
onPasswordChange.handleNavResult { result ->
158+
if (result) {
159+
viewModel.onPasswordUpdate()
160+
}
161+
}
162+
163+
onExpirationChange.handleNavResult { result ->
164+
if (result) {
165+
viewModel.onExpirationUpdate()
134166
}
135167
}
136168
}
@@ -179,46 +211,6 @@ private fun EnableLinkSection(
179211
}
180212
}
181213

182-
@Composable
183-
private fun PublicLinkSection(
184-
url: String?,
185-
onShareLink: () -> Unit,
186-
onCopyLink: () -> Unit,
187-
) {
188-
Column {
189-
Text(
190-
text = stringResource(R.string.share_link).uppercase(),
191-
style = typography().title03,
192-
modifier = Modifier.padding(dimensions().spacing16x)
193-
)
194-
195-
Column(
196-
modifier = Modifier
197-
.fillMaxWidth()
198-
.background(colorsScheme().surface)
199-
.padding(dimensions().spacing16x)
200-
) {
201-
Text(
202-
text = url ?: stringResource(R.string.creating_link),
203-
style = typography().body01,
204-
minLines = 2,
205-
)
206-
207-
Spacer(modifier = Modifier.height(dimensions().spacing24x))
208-
209-
WireSecondaryButton(
210-
text = stringResource(R.string.share_link),
211-
onClick = onShareLink
212-
)
213-
Spacer(modifier = Modifier.height(dimensions().spacing8x))
214-
WireSecondaryButton(
215-
text = stringResource(R.string.copy_link),
216-
onClick = onCopyLink
217-
)
218-
}
219-
}
220-
}
221-
222214
/**
223215
* Show a toast message when the link is copied to the clipboard.
224216
* Only for API levels lower than 33. On new versions, the system will show a clipboard
@@ -230,6 +222,16 @@ private fun showLinkCopiedToast(context: Context) {
230222
}
231223
}
232224

225+
@Composable
226+
private fun <D : DestinationSpec<*>, R> ResultRecipient<D, R>.handleNavResult(block: (R) -> Unit) {
227+
onNavResult { result ->
228+
when (result) {
229+
is NavResult.Value<R> -> block(result.value)
230+
NavResult.Canceled -> {}
231+
}
232+
}
233+
}
234+
233235
@PreviewMultipleThemes
234236
@Composable
235237
private fun PreviewCreatePublicLinkScreen() {
@@ -241,7 +243,7 @@ private fun PreviewCreatePublicLinkScreen() {
241243
onCheckChange = {}
242244
)
243245
PublicLinkSection(
244-
url = "http://test.url",
246+
state = PublicLinkState.READY,
245247
onShareLink = {},
246248
onCopyLink = {}
247249
)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2025 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*/
18+
package com.wire.android.feature.cells.ui.publiclink
19+
20+
import androidx.compose.foundation.background
21+
import androidx.compose.foundation.layout.Column
22+
import androidx.compose.foundation.layout.Spacer
23+
import androidx.compose.foundation.layout.fillMaxWidth
24+
import androidx.compose.foundation.layout.height
25+
import androidx.compose.foundation.layout.padding
26+
import androidx.compose.material3.Text
27+
import androidx.compose.runtime.Composable
28+
import androidx.compose.ui.Modifier
29+
import androidx.compose.ui.res.stringResource
30+
import com.wire.android.feature.cells.R
31+
import com.wire.android.ui.common.button.WireButtonState
32+
import com.wire.android.ui.common.button.WireSecondaryButton
33+
import com.wire.android.ui.common.colorsScheme
34+
import com.wire.android.ui.common.dimensions
35+
import com.wire.android.ui.common.typography
36+
37+
@Composable
38+
internal fun PublicLinkSection(
39+
state: PublicLinkState,
40+
onShareLink: () -> Unit,
41+
onCopyLink: () -> Unit,
42+
) {
43+
44+
val isLoading = state == PublicLinkState.LOADING
45+
46+
Column {
47+
Text(
48+
text = stringResource(R.string.share_link).uppercase(),
49+
style = typography().title03,
50+
modifier = Modifier.padding(dimensions().spacing16x)
51+
)
52+
53+
Column(
54+
modifier = Modifier
55+
.fillMaxWidth()
56+
.background(colorsScheme().surface)
57+
.padding(dimensions().spacing16x)
58+
) {
59+
WireSecondaryButton(
60+
text = stringResource(R.string.share_link),
61+
state = if (isLoading) WireButtonState.Disabled else WireButtonState.Default,
62+
loading = isLoading,
63+
onClick = onShareLink,
64+
)
65+
Spacer(modifier = Modifier.height(dimensions().spacing8x))
66+
WireSecondaryButton(
67+
text = stringResource(R.string.copy_link),
68+
state = if (isLoading) WireButtonState.Disabled else WireButtonState.Default,
69+
loading = isLoading,
70+
onClick = onCopyLink
71+
)
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)