From a089fdfda2e143b365d7bcdd62d11a762e38c287 Mon Sep 17 00:00:00 2001 From: Oussama Hassine Date: Wed, 25 Oct 2023 14:14:31 +0200 Subject: [PATCH] feat: Show e2ei certificate in device details screen - PART 2 (WPB-3214) (#2332) --- .../topappbar/WireCenterAlignedTopAppBar.kt | 1 - .../e2ei/E2eiCertificateDetailsBottomSheet.kt | 93 +++++++++++ .../e2ei/E2eiCertificateDetailsScreen.kt | 157 ++++++++++++++++++ .../E2eiCertificateDetailsScreenNavArgs.kt | 22 +++ .../e2ei/E2eiCertificateDetailsViewModel.kt | 46 +++++ .../com/wire/android/util/ClipboardCopier.kt | 4 +- .../kotlin/com/wire/android/util/FileUtil.kt | 17 ++ app/src/main/res/values/strings.xml | 5 + 8 files changed, 342 insertions(+), 3 deletions(-) create mode 100644 app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsBottomSheet.kt create mode 100644 app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsScreen.kt create mode 100644 app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsScreenNavArgs.kt create mode 100644 app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsViewModel.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/WireCenterAlignedTopAppBar.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/WireCenterAlignedTopAppBar.kt index 93847b255d..27ca4b0746 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/WireCenterAlignedTopAppBar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/WireCenterAlignedTopAppBar.kt @@ -39,7 +39,6 @@ import com.wire.android.ui.common.dimensions import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.theme.wireTypography -@OptIn(ExperimentalMaterial3Api::class) @Composable fun WireCenterAlignedTopAppBar( title: String, diff --git a/app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsBottomSheet.kt b/app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsBottomSheet.kt new file mode 100644 index 0000000000..1ae656397d --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsBottomSheet.kt @@ -0,0 +1,93 @@ +/* + * Wire + * Copyright (C) 2023 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.settings.devices.e2ei + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.wire.android.R +import com.wire.android.ui.common.bottomsheet.MenuBottomSheetItem +import com.wire.android.ui.common.bottomsheet.MenuItemIcon +import com.wire.android.ui.common.bottomsheet.MenuModalSheetContent +import com.wire.android.ui.common.bottomsheet.MenuModalSheetHeader +import com.wire.android.ui.common.bottomsheet.WireModalSheetLayout +import com.wire.android.ui.common.bottomsheet.WireModalSheetState + +@Composable +fun E2eiCertificateDetailsBottomSheet( + sheetState: WireModalSheetState, + onCopyToClipboard: () -> Unit, + onDownload: () -> Unit, +) { + val coroutineScope = rememberCoroutineScope() + + WireModalSheetLayout(sheetState = sheetState, coroutineScope = coroutineScope) { + MenuModalSheetContent( + header = MenuModalSheetHeader.Gone, + menuItems = buildList { + add { + CreateCertificateSheetItem( + title = stringResource(R.string.e2ei_certificate_details_copy_to_clipboard), + icon = R.drawable.ic_copy, + onClicked = onCopyToClipboard, + enabled = true + ) + } + add { + CreateCertificateSheetItem( + title = stringResource(R.string.e2ei_certificate_details_download), + icon = R.drawable.ic_download, + onClicked = onDownload, + enabled = true + ) + } + } + ) + } +} + +@Composable +private fun CreateCertificateSheetItem( + title: String, + icon: Int, + onClicked: () -> Unit, + enabled: Boolean = true, +) { + MenuBottomSheetItem( + title = title, + onItemClick = onClicked, + icon = { + MenuItemIcon( + id = icon, + contentDescription = "", + ) + }, + enabled = enabled + ) +} + +@Preview +@Composable +fun PreviewE2eiCertificateDetailsBottomSheet() { + E2eiCertificateDetailsBottomSheet( + sheetState = WireModalSheetState(), + onCopyToClipboard = { }, + onDownload = { } + ) +} diff --git a/app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsScreen.kt new file mode 100644 index 0000000000..a413017156 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsScreen.kt @@ -0,0 +1,157 @@ +/* + * Wire + * Copyright (C) 2023 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.settings.devices.e2ei + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.annotation.RootNavGraph +import com.wire.android.R +import com.wire.android.navigation.Navigator +import com.wire.android.navigation.style.PopUpNavigationAnimation +import com.wire.android.ui.common.button.WireSecondaryIconButton +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.snackbar.LocalSnackbarHostState +import com.wire.android.ui.common.topappbar.NavigationIconType +import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar +import com.wire.android.util.copyLinkToClipboard +import com.wire.android.util.createPemFile +import com.wire.android.util.saveFileToDownloadsFolder +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okio.Path.Companion.toOkioPath + +@RootNavGraph +@Destination( + navArgsDelegate = E2eiCertificateDetailsScreenNavArgs::class, + style = PopUpNavigationAnimation::class, +) +@Composable +fun E2eiCertificateDetailsScreen( + e2eiCertificateDetailsViewModel: E2eiCertificateDetailsViewModel = hiltViewModel(), + navigator: Navigator +) { + val snackbarHostState = LocalSnackbarHostState.current + val scope = rememberCoroutineScope() + val context = LocalContext.current + + WireScaffold( + topBar = { + WireCenterAlignedTopAppBar( + onNavigationPressed = navigator::navigateBack, + title = stringResource(R.string.e2ei_certificate_details_screen_title), + navigationIconType = NavigationIconType.Back, + actions = { + WireSecondaryIconButton( + onButtonClicked = { + e2eiCertificateDetailsViewModel.state.wireModalSheetState.show() + }, + iconResource = R.drawable.ic_more, + contentDescription = R.string.content_description_more_options + ) + } + ) + } + ) { + val clipboardManager = LocalClipboardManager.current + + with(e2eiCertificateDetailsViewModel) { + val copiedToClipboardString = + stringResource(id = R.string.e2ei_certificate_details_certificate_copied_to_clipboard) + val downloadedString = stringResource(id = R.string.media_gallery_on_image_downloaded) + + E2eiCertificateDetailsContent( + padding = it, + certificateString = getCertificate() + ) + E2eiCertificateDetailsBottomSheet( + sheetState = state.wireModalSheetState, + onCopyToClipboard = { + clipboardManager.copyLinkToClipboard(getCertificate()) + scope.launch { + state.wireModalSheetState.hide() + snackbarHostState.showSnackbar(copiedToClipboardString) + } + }, + onDownload = { + scope.launch { + withContext(Dispatchers.IO) { + createPemFile(CERTIFICATE_FILE_NAME, getCertificate()).also { + saveFileToDownloadsFolder( + context = context, + assetName = CERTIFICATE_FILE_NAME, + assetDataPath = it.toPath().toOkioPath(), + assetDataSize = it.length() + ) + } + } + state.wireModalSheetState.hide() + snackbarHostState.showSnackbar(downloadedString) + } + } + ) + } + } +} + +@Composable +fun E2eiCertificateDetailsContent( + padding: PaddingValues, + certificateString: String +) { + val textStyle = TextStyle( + textAlign = TextAlign.Justify, + fontSize = 12.sp, + lineHeight = 18.sp, + color = colorsScheme().onBackground + ) + val scroll = rememberScrollState(0) + Text( + modifier = Modifier + .verticalScroll(scroll) + .padding( + top = padding.calculateTopPadding() + dimensions().spacing16x, + start = padding.calculateStartPadding(LayoutDirection.Ltr) + dimensions().spacing16x, + end = padding.calculateEndPadding(LayoutDirection.Ltr) + dimensions().spacing16x, + bottom = padding.calculateBottomPadding() + ), + text = certificateString, + style = textStyle + ) +} + +const val CERTIFICATE_FILE_NAME = "certificate.pem" diff --git a/app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsScreenNavArgs.kt b/app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsScreenNavArgs.kt new file mode 100644 index 0000000000..9ebe51da34 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsScreenNavArgs.kt @@ -0,0 +1,22 @@ +/* + * Wire + * Copyright (C) 2023 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.settings.devices.e2ei + +data class E2eiCertificateDetailsScreenNavArgs( + val certificateString: String +) diff --git a/app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsViewModel.kt new file mode 100644 index 0000000000..4e77cdbbb1 --- /dev/null +++ b/app/src/main/kotlin/com/wire/android/ui/settings/devices/e2ei/E2eiCertificateDetailsViewModel.kt @@ -0,0 +1,46 @@ +/* + * Wire + * Copyright (C) 2023 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.settings.devices.e2ei + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import com.wire.android.ui.common.bottomsheet.WireModalSheetState +import com.wire.android.ui.navArgs +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class E2eiCertificateDetailsViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, +) : ViewModel() { + + var state: E2eiCertificateDetailsState by mutableStateOf(E2eiCertificateDetailsState()) + private set + + private val e2eiCertificateDetailsScreenNavArgs: E2eiCertificateDetailsScreenNavArgs = + savedStateHandle.navArgs() + + fun getCertificate() = e2eiCertificateDetailsScreenNavArgs.certificateString +} + +data class E2eiCertificateDetailsState( + val wireModalSheetState: WireModalSheetState = WireModalSheetState() +) diff --git a/app/src/main/kotlin/com/wire/android/util/ClipboardCopier.kt b/app/src/main/kotlin/com/wire/android/util/ClipboardCopier.kt index 2d0135f6e4..d75dc4a8d3 100644 --- a/app/src/main/kotlin/com/wire/android/util/ClipboardCopier.kt +++ b/app/src/main/kotlin/com/wire/android/util/ClipboardCopier.kt @@ -23,6 +23,6 @@ package com.wire.android.util import androidx.compose.ui.platform.ClipboardManager import androidx.compose.ui.text.AnnotatedString -fun ClipboardManager.copyLinkToClipboard(link: String) { - setText(AnnotatedString(link)) +fun ClipboardManager.copyLinkToClipboard(text: String) { + setText(AnnotatedString(text)) } diff --git a/app/src/main/kotlin/com/wire/android/util/FileUtil.kt b/app/src/main/kotlin/com/wire/android/util/FileUtil.kt index 63edb30291..b8938b5cc9 100644 --- a/app/src/main/kotlin/com/wire/android/util/FileUtil.kt +++ b/app/src/main/kotlin/com/wire/android/util/FileUtil.kt @@ -58,6 +58,7 @@ import com.wire.android.util.dispatchers.DispatcherProvider import com.wire.kalium.logic.data.asset.isAudioMimeType import com.wire.kalium.logic.util.buildFileName import com.wire.kalium.logic.util.splitFileExtensionAndCopyCounter +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okio.Path import java.io.File @@ -112,6 +113,22 @@ fun getTempWritableAttachmentUri(context: Context, attachmentPath: Path): Uri { return FileProvider.getUriForFile(context, context.getProviderAuthority(), file) } +suspend fun createPemFile( + pathname: String, + content: String +): File { + return withContext(Dispatchers.IO) { + return@withContext File( + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS + ), + pathname + ).apply { + writeText(content) + } + } +} + private fun Context.saveFileDataToDownloadsFolder(assetName: String, downloadedDataPath: Path, fileSize: Long): Uri? { val resolver = contentResolver val mimeType = Uri.parse(downloadedDataPath.toString()).getMimeType(this@saveFileDataToDownloadsFolder) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d96f29a87d..7213eacf5e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1239,4 +1239,9 @@ Authenticate with biometrics To unlock Wire Use passcode + + Certificate Details + Copy to Clipboard + Download + Certificate copied to clipboard