Skip to content

Commit

Permalink
feat: Show e2ei certificate in device details screen - PART 2 (WPB-32…
Browse files Browse the repository at this point in the history
…14) (#2332)
  • Loading branch information
ohassine committed Oct 25, 2023
1 parent 1385f09 commit a089fdf
Show file tree
Hide file tree
Showing 8 changed files with 342 additions and 3 deletions.
Expand Up @@ -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,
Expand Down
@@ -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 = { }
)
}
@@ -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"
@@ -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
)
@@ -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()
)
4 changes: 2 additions & 2 deletions app/src/main/kotlin/com/wire/android/util/ClipboardCopier.kt
Expand Up @@ -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))
}
17 changes: 17 additions & 0 deletions app/src/main/kotlin/com/wire/android/util/FileUtil.kt
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/res/values/strings.xml
Expand Up @@ -1239,4 +1239,9 @@
<string name="biometrics_prompt_dialog_title">Authenticate with biometrics</string>
<string name="biometrics_prompt_dialog_subtitle">To unlock Wire</string>
<string name="biometrics_use_passcode_button">Use passcode</string>
<!-- e2ei-->
<string name="e2ei_certificate_details_screen_title">Certificate Details</string>
<string name="e2ei_certificate_details_copy_to_clipboard">Copy to Clipboard</string>
<string name="e2ei_certificate_details_download">Download</string>
<string name="e2ei_certificate_details_certificate_copied_to_clipboard">Certificate copied to clipboard</string>
</resources>

0 comments on commit a089fdf

Please sign in to comment.