mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-26 18:40:45 +00:00
Add QR to recovery password screen
This commit is contained in:
parent
ea0bcbe7c5
commit
9ac3ec22c0
@ -28,6 +28,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
@ -38,6 +39,8 @@ import org.thoughtcrime.securesms.home.HomeActivity
|
|||||||
import org.thoughtcrime.securesms.notifications.PushRegistry
|
import org.thoughtcrime.securesms.notifications.PushRegistry
|
||||||
import org.thoughtcrime.securesms.ui.AppTheme
|
import org.thoughtcrime.securesms.ui.AppTheme
|
||||||
import org.thoughtcrime.securesms.ui.OutlineButton
|
import org.thoughtcrime.securesms.ui.OutlineButton
|
||||||
|
import org.thoughtcrime.securesms.ui.PreviewTheme
|
||||||
|
import org.thoughtcrime.securesms.ui.ThemeResPreviewParameterProvider
|
||||||
import org.thoughtcrime.securesms.ui.h8
|
import org.thoughtcrime.securesms.ui.h8
|
||||||
import org.thoughtcrime.securesms.ui.h9
|
import org.thoughtcrime.securesms.ui.h9
|
||||||
import org.thoughtcrime.securesms.ui.session_accent
|
import org.thoughtcrime.securesms.ui.session_accent
|
||||||
@ -66,8 +69,10 @@ class MessageNotificationsActivity : BaseActionBarActivity() {
|
|||||||
private fun MessageNotifications() {
|
private fun MessageNotifications() {
|
||||||
val state by viewModel.stateFlow.collectAsState()
|
val state by viewModel.stateFlow.collectAsState()
|
||||||
|
|
||||||
|
AppTheme {
|
||||||
MessageNotifications(state, viewModel::setEnabled, ::register)
|
MessageNotifications(state, viewModel::setEnabled, ::register)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun register() {
|
private fun register() {
|
||||||
TextSecurePreferences.setPushEnabled(this, viewModel.stateFlow.value.pushEnabled)
|
TextSecurePreferences.setPushEnabled(this, viewModel.stateFlow.value.pushEnabled)
|
||||||
@ -81,13 +86,21 @@ class MessageNotificationsActivity : BaseActionBarActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun MessageNotificationsPreview(
|
||||||
|
@PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int
|
||||||
|
) {
|
||||||
|
PreviewTheme(themeResId) {
|
||||||
|
MessageNotifications()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageNotifications(
|
fun MessageNotifications(
|
||||||
state: MessageNotificationsState = MessageNotificationsState(),
|
state: MessageNotificationsState = MessageNotificationsState(),
|
||||||
setEnabled: (Boolean) -> Unit = {},
|
setEnabled: (Boolean) -> Unit = {},
|
||||||
onContinue: () -> Unit = {}
|
onContinue: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
AppTheme {
|
|
||||||
Column(Modifier.padding(horizontal = 32.dp)) {
|
Column(Modifier.padding(horizontal = 32.dp)) {
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
Text("Message notifications", style = MaterialTheme.typography.h4)
|
Text("Message notifications", style = MaterialTheme.typography.h4)
|
||||||
@ -119,7 +132,6 @@ fun MessageNotifications(
|
|||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NotificationRadioButton(
|
fun NotificationRadioButton(
|
||||||
|
@ -4,22 +4,39 @@ import android.content.ClipData
|
|||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.Card
|
||||||
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
@ -33,7 +50,10 @@ import org.thoughtcrime.securesms.BaseActionBarActivity
|
|||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
||||||
import org.thoughtcrime.securesms.ui.AppTheme
|
import org.thoughtcrime.securesms.ui.AppTheme
|
||||||
|
import org.thoughtcrime.securesms.ui.Cell
|
||||||
|
import org.thoughtcrime.securesms.ui.CellNoMargin
|
||||||
import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
|
import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
|
||||||
|
import org.thoughtcrime.securesms.ui.LocalExtraColors
|
||||||
import org.thoughtcrime.securesms.ui.OutlineButton
|
import org.thoughtcrime.securesms.ui.OutlineButton
|
||||||
import org.thoughtcrime.securesms.ui.PreviewTheme
|
import org.thoughtcrime.securesms.ui.PreviewTheme
|
||||||
import org.thoughtcrime.securesms.ui.SessionShieldIcon
|
import org.thoughtcrime.securesms.ui.SessionShieldIcon
|
||||||
@ -42,27 +62,21 @@ import org.thoughtcrime.securesms.ui.classicDarkColors
|
|||||||
import org.thoughtcrime.securesms.ui.colorDestructive
|
import org.thoughtcrime.securesms.ui.colorDestructive
|
||||||
import org.thoughtcrime.securesms.ui.extraSmall
|
import org.thoughtcrime.securesms.ui.extraSmall
|
||||||
import org.thoughtcrime.securesms.ui.h8
|
import org.thoughtcrime.securesms.ui.h8
|
||||||
|
import org.thoughtcrime.securesms.ui.small
|
||||||
|
|
||||||
class RecoveryPasswordActivity : BaseActionBarActivity() {
|
class RecoveryPasswordActivity : BaseActionBarActivity() {
|
||||||
|
|
||||||
private val seed by lazy {
|
private val viewModel: RecoveryPasswordViewModel by viewModels()
|
||||||
var hexEncodedSeed = IdentityKeyUtil.retrieve(this, IdentityKeyUtil.LOKI_SEED)
|
|
||||||
if (hexEncodedSeed == null) {
|
|
||||||
hexEncodedSeed = IdentityKeyUtil.getIdentityKeyPair(this).hexEncodedPrivateKey // Legacy account
|
|
||||||
}
|
|
||||||
val loadFileContents: (String) -> String = { fileName ->
|
|
||||||
MnemonicUtilities.loadFileContents(this, fileName)
|
|
||||||
}
|
|
||||||
MnemonicCodec(loadFileContents).encode(hexEncodedSeed!!, MnemonicCodec.Language.Configuration.english)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
supportActionBar!!.title = resources.getString(R.string.activity_recovery_password)
|
supportActionBar!!.title = resources.getString(R.string.activity_recovery_password)
|
||||||
|
|
||||||
ComposeView(this)
|
ComposeView(this).apply {
|
||||||
.apply { setContent { RecoveryPassword() } }
|
setContent {
|
||||||
.let(::setContentView)
|
RecoveryPassword(viewModel.seed, viewModel.bitmap) { copySeed() }
|
||||||
|
}
|
||||||
|
}.let(::setContentView)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun revealSeed() {
|
private fun revealSeed() {
|
||||||
@ -72,7 +86,7 @@ class RecoveryPasswordActivity : BaseActionBarActivity() {
|
|||||||
private fun copySeed() {
|
private fun copySeed() {
|
||||||
revealSeed()
|
revealSeed()
|
||||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
val clip = ClipData.newPlainText("Seed", seed)
|
val clip = ClipData.newPlainText("Seed", viewModel.seed)
|
||||||
clipboard.setPrimaryClip(clip)
|
clipboard.setPrimaryClip(clip)
|
||||||
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
@ -84,23 +98,30 @@ fun PreviewMessageDetails(
|
|||||||
@PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int
|
@PreviewParameter(ThemeResPreviewParameterProvider::class) themeResId: Int
|
||||||
) {
|
) {
|
||||||
PreviewTheme(themeResId) {
|
PreviewTheme(themeResId) {
|
||||||
RecoveryPassword()
|
RecoveryPassword(seed = "Voyage urban toyed maverick peculiar tuxedo penguin tree grass building listen speak withdraw terminal plane")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RecoveryPassword() {
|
fun RecoveryPassword(seed: String = "", bitmap: Bitmap? = null, copySeed:() -> Unit = {}) {
|
||||||
AppTheme {
|
AppTheme {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
Column(
|
||||||
RecoveryPasswordCell()
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
|
) {
|
||||||
|
RecoveryPasswordCell(seed, bitmap, copySeed)
|
||||||
HideRecoveryPasswordCell()
|
HideRecoveryPasswordCell()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RecoveryPasswordCell() {
|
fun RecoveryPasswordCell(seed: String = "", bitmap: Bitmap? = null, copySeed:() -> Unit = {}) {
|
||||||
|
val showQr = remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
CellWithPaddingAndMargin {
|
CellWithPaddingAndMargin {
|
||||||
Column {
|
Column {
|
||||||
Row {
|
Row {
|
||||||
@ -111,8 +132,9 @@ fun RecoveryPasswordCell() {
|
|||||||
|
|
||||||
Text("Use your recovery password to load your account on new devices.\n\nYour account cannot be recovered without your recovery password. Make sure it's stored somewhere safe and secure — and don't share it with anyone.")
|
Text("Use your recovery password to load your account on new devices.\n\nYour account cannot be recovered without your recovery password. Make sure it's stored somewhere safe and secure — and don't share it with anyone.")
|
||||||
|
|
||||||
|
AnimatedVisibility(!showQr.value) {
|
||||||
Text(
|
Text(
|
||||||
"Voyage urban toyed maverick peculiar tuxedo penguin tree grass building listen speak withdraw terminal plane",
|
seed,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = 24.dp)
|
.padding(vertical = 24.dp)
|
||||||
.border(
|
.border(
|
||||||
@ -121,17 +143,60 @@ fun RecoveryPasswordCell() {
|
|||||||
shape = RoundedCornerShape(11.dp)
|
shape = RoundedCornerShape(11.dp)
|
||||||
)
|
)
|
||||||
.padding(24.dp),
|
.padding(24.dp),
|
||||||
style = MaterialTheme.typography.extraSmall.copy(fontFamily = FontFamily.Monospace)
|
style = MaterialTheme.typography.small.copy(fontFamily = FontFamily.Monospace),
|
||||||
|
color = LocalExtraColors.current.prominentButtonColor,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(showQr.value, modifier = Modifier.align(Alignment.CenterHorizontally)) {
|
||||||
|
Card(
|
||||||
|
backgroundColor = Color.White,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
.padding(vertical = 24.dp)
|
||||||
|
) {
|
||||||
|
Box {
|
||||||
|
bitmap?.let {
|
||||||
|
Image(
|
||||||
|
bitmap = it.asImageBitmap(),
|
||||||
|
contentDescription = "some useful description",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.session_shield),
|
||||||
|
contentDescription = "",
|
||||||
|
tint = Color.Black,
|
||||||
|
modifier = Modifier.align(Alignment.Center)
|
||||||
|
.width(46.dp)
|
||||||
|
.height(56.dp)
|
||||||
|
.background(color = Color.White)
|
||||||
|
.padding(horizontal = 3.dp, vertical = 1.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(!showQr.value) {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(32.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(32.dp)) {
|
||||||
OutlineButton(text = stringResource(R.string.copy), modifier = Modifier.weight(1f), color = MaterialTheme.colors.onPrimary) {}
|
OutlineButton(text = stringResource(R.string.copy), modifier = Modifier.weight(1f), color = MaterialTheme.colors.onPrimary) { copySeed() }
|
||||||
OutlineButton(text = "View QR", modifier = Modifier.weight(1f), color = MaterialTheme.colors.onPrimary) {}
|
OutlineButton(text = "View QR", modifier = Modifier.weight(1f), color = MaterialTheme.colors.onPrimary) { showQr.toggle() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(showQr.value, modifier = Modifier.align(Alignment.CenterHorizontally)) {
|
||||||
|
OutlineButton(
|
||||||
|
text = "View Password",
|
||||||
|
color = MaterialTheme.colors.onPrimary,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
) { showQr.toggle() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun MutableState<Boolean>.toggle() { value = !value }
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HideRecoveryPasswordCell() {
|
fun HideRecoveryPasswordCell() {
|
||||||
CellWithPaddingAndMargin {
|
CellWithPaddingAndMargin {
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
package org.thoughtcrime.securesms.onboarding.recoverypassword
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.session.libsignal.crypto.MnemonicCodec
|
||||||
|
import org.session.libsignal.utilities.hexEncodedPrivateKey
|
||||||
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
|
||||||
|
import org.thoughtcrime.securesms.crypto.MnemonicUtilities
|
||||||
|
import org.thoughtcrime.securesms.util.QRCodeUtilities
|
||||||
|
import org.thoughtcrime.securesms.util.toPx
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class RecoveryPasswordViewModel @Inject constructor(
|
||||||
|
private val application: Application
|
||||||
|
): AndroidViewModel(application) {
|
||||||
|
|
||||||
|
val bitmap: Bitmap? = TextSecurePreferences.getLocalNumber(application)?.let {
|
||||||
|
QRCodeUtilities.encode(
|
||||||
|
data = it,
|
||||||
|
size = toPx(280, application.resources),
|
||||||
|
isInverted = false,
|
||||||
|
hasTransparentBackground = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val seed by lazy {
|
||||||
|
val hexEncodedSeed = IdentityKeyUtil.retrieve(application, IdentityKeyUtil.LOKI_SEED)
|
||||||
|
?: IdentityKeyUtil.getIdentityKeyPair(application).hexEncodedPrivateKey // Legacy account
|
||||||
|
MnemonicCodec { MnemonicUtilities.loadFileContents(application, it) }
|
||||||
|
.encode(hexEncodedSeed, MnemonicCodec.Language.Configuration.english)
|
||||||
|
}
|
||||||
|
}
|
@ -9,17 +9,26 @@ import com.google.zxing.qrcode.QRCodeWriter
|
|||||||
|
|
||||||
object QRCodeUtilities {
|
object QRCodeUtilities {
|
||||||
|
|
||||||
fun encode(data: String, size: Int, isInverted: Boolean = false, hasTransparentBackground: Boolean = true): Bitmap {
|
fun encode(
|
||||||
|
data: String,
|
||||||
|
size: Int,
|
||||||
|
isInverted: Boolean = false,
|
||||||
|
hasTransparentBackground: Boolean = true,
|
||||||
|
dark: Int = Color.BLACK,
|
||||||
|
light: Int = Color.WHITE,
|
||||||
|
): Bitmap {
|
||||||
try {
|
try {
|
||||||
val hints = hashMapOf( EncodeHintType.MARGIN to 1 )
|
val hints = hashMapOf( EncodeHintType.MARGIN to 1 )
|
||||||
val result = QRCodeWriter().encode(data, BarcodeFormat.QR_CODE, size, size, hints)
|
val result = QRCodeWriter().encode(data, BarcodeFormat.QR_CODE, size, size, hints)
|
||||||
val bitmap = Bitmap.createBitmap(result.width, result.height, Bitmap.Config.ARGB_8888)
|
val bitmap = Bitmap.createBitmap(result.width, result.height, Bitmap.Config.ARGB_8888)
|
||||||
|
val color = if (isInverted) light else dark
|
||||||
|
val background = if (isInverted) dark else light
|
||||||
for (y in 0 until result.height) {
|
for (y in 0 until result.height) {
|
||||||
for (x in 0 until result.width) {
|
for (x in 0 until result.width) {
|
||||||
if (result.get(x, y)) {
|
if (result.get(x, y)) {
|
||||||
bitmap.setPixel(x, y, if (isInverted) Color.WHITE else Color.BLACK)
|
bitmap.setPixel(x, y, color)
|
||||||
} else if (!hasTransparentBackground) {
|
} else if (!hasTransparentBackground) {
|
||||||
bitmap.setPixel(x, y, if (isInverted) Color.BLACK else Color.WHITE)
|
bitmap.setPixel(x, y, background)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user