Update settings

This commit is contained in:
Andrew 2024-04-11 14:20:32 +09:30
parent 85c7a23235
commit c589bed249
12 changed files with 447 additions and 637 deletions

View File

@ -373,6 +373,7 @@ dependencies {
implementation "com.google.accompanist:accompanist-pager:0.33.1-alpha" implementation "com.google.accompanist:accompanist-pager:0.33.1-alpha"
implementation "com.google.accompanist:accompanist-pager-indicators:0.33.1-alpha" implementation "com.google.accompanist:accompanist-pager-indicators:0.33.1-alpha"
implementation "com.google.accompanist:accompanist-permissions:0.33.1-alpha" implementation "com.google.accompanist:accompanist-permissions:0.33.1-alpha"
implementation "com.google.accompanist:accompanist-drawablepainter:0.33.1-alpha"
implementation "androidx.camera:camera-camera2:1.3.2" implementation "androidx.camera:camera-camera2:1.3.2"
implementation "androidx.camera:camera-lifecycle:1.3.2" implementation "androidx.camera:camera-lifecycle:1.3.2"

View File

@ -161,7 +161,7 @@ import org.thoughtcrime.securesms.mms.MediaConstraints
import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.mms.Slide
import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.mms.VideoSlide import org.thoughtcrime.securesms.mms.VideoSlide
import org.thoughtcrime.securesms.onboarding.recoverypassword.startRecoveryPasswordActivity import org.thoughtcrime.securesms.onboarding.recoverypassword.RecoveryPasswordActivity
import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
@ -176,6 +176,7 @@ import org.thoughtcrime.securesms.util.isScrolledToBottom
import org.thoughtcrime.securesms.util.isScrolledToWithin30dpOfBottom import org.thoughtcrime.securesms.util.isScrolledToWithin30dpOfBottom
import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.push
import org.thoughtcrime.securesms.util.show import org.thoughtcrime.securesms.util.show
import org.thoughtcrime.securesms.util.start
import org.thoughtcrime.securesms.util.toPx import org.thoughtcrime.securesms.util.toPx
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.Locale import java.util.Locale
@ -1602,7 +1603,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val userPublicKey = textSecurePreferences.getLocalNumber() val userPublicKey = textSecurePreferences.getLocalNumber()
val isNoteToSelf = (recipient.isContactRecipient && recipient.address.toString() == userPublicKey) val isNoteToSelf = (recipient.isContactRecipient && recipient.address.toString() == userPublicKey)
if (text.contains(seed) && !isNoteToSelf && !hasPermissionToSendSeed) { if (text.contains(seed) && !isNoteToSelf && !hasPermissionToSendSeed) {
startRecoveryPasswordActivity() start<RecoveryPasswordActivity>()
} }
// Create the message // Create the message
val message = VisibleMessage().applyExpiryMode(viewModel.threadId) val message = VisibleMessage().applyExpiryMode(viewModel.threadId)

View File

@ -92,7 +92,7 @@ import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.notifications.PushRegistry import org.thoughtcrime.securesms.notifications.PushRegistry
import org.thoughtcrime.securesms.onboarding.recoverypassword.startRecoveryPasswordActivity import org.thoughtcrime.securesms.onboarding.recoverypassword.RecoveryPasswordActivity
import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.preferences.SettingsActivity import org.thoughtcrime.securesms.preferences.SettingsActivity
import org.thoughtcrime.securesms.showMuteDialog import org.thoughtcrime.securesms.showMuteDialog
@ -111,6 +111,7 @@ import org.thoughtcrime.securesms.util.IP2Country
import org.thoughtcrime.securesms.util.disableClipping import org.thoughtcrime.securesms.util.disableClipping
import org.thoughtcrime.securesms.util.push import org.thoughtcrime.securesms.util.push
import org.thoughtcrime.securesms.util.show import org.thoughtcrime.securesms.util.show
import org.thoughtcrime.securesms.util.start
import org.thoughtcrime.securesms.util.themeState import org.thoughtcrime.securesms.util.themeState
import java.io.IOException import java.io.IOException
import java.util.Locale import java.util.Locale
@ -374,7 +375,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
stringResource(R.string.continue_2), stringResource(R.string.continue_2),
Modifier.align(Alignment.CenterVertically), Modifier.align(Alignment.CenterVertically),
contentDescription = GetString(R.string.AccessibilityId_reveal_recovery_phrase_button) contentDescription = GetString(R.string.AccessibilityId_reveal_recovery_phrase_button)
) { startRecoveryPasswordActivity() } ) { start<RecoveryPasswordActivity>() }
} }
} }
} }

View File

@ -1,27 +1,21 @@
package org.thoughtcrime.securesms.onboarding.recoverypassword package org.thoughtcrime.securesms.onboarding.recoverypassword
import android.app.Activity
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 androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.animation.AnimatedVisibility 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.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll 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
@ -30,10 +24,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember 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.ColorFilter
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
@ -52,6 +43,7 @@ import org.thoughtcrime.securesms.ui.SessionShieldIcon
import org.thoughtcrime.securesms.ui.ThemeResPreviewParameterProvider import org.thoughtcrime.securesms.ui.ThemeResPreviewParameterProvider
import org.thoughtcrime.securesms.ui.classicDarkColors import org.thoughtcrime.securesms.ui.classicDarkColors
import org.thoughtcrime.securesms.ui.colorDestructive import org.thoughtcrime.securesms.ui.colorDestructive
import org.thoughtcrime.securesms.ui.components.QrImageCard
import org.thoughtcrime.securesms.ui.contentDescription import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.h8 import org.thoughtcrime.securesms.ui.h8
import org.thoughtcrime.securesms.ui.small import org.thoughtcrime.securesms.ui.small
@ -68,7 +60,6 @@ class RecoveryPasswordActivity : BaseActionBarActivity() {
setContent { setContent {
RecoveryPassword( RecoveryPassword(
viewModel.seed, viewModel.seed,
viewModel.qrBitmap,
{ viewModel.copySeed(context) } { viewModel.copySeed(context) }
) { onHide() } ) { onHide() }
} }
@ -113,7 +104,6 @@ fun PreviewRecoveryPassword(
@Composable @Composable
fun RecoveryPassword( fun RecoveryPassword(
seed: String = "", seed: String = "",
qrBitmap: Bitmap? = null,
copySeed:() -> Unit = {}, copySeed:() -> Unit = {},
onHide:() -> Unit = {} onHide:() -> Unit = {}
) { ) {
@ -124,14 +114,14 @@ fun RecoveryPassword(
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
.padding(bottom = 16.dp) .padding(bottom = 16.dp)
) { ) {
RecoveryPasswordCell(seed, qrBitmap, copySeed) RecoveryPasswordCell(seed, copySeed)
HideRecoveryPasswordCell(onHide) HideRecoveryPasswordCell(onHide)
} }
} }
} }
@Composable @Composable
fun RecoveryPasswordCell(seed: String = "", qrBitmap: Bitmap? = null, copySeed:() -> Unit = {}) { fun RecoveryPasswordCell(seed: String, copySeed:() -> Unit = {}) {
val showQr = remember { val showQr = remember {
mutableStateOf(false) mutableStateOf(false)
} }
@ -163,21 +153,15 @@ fun RecoveryPasswordCell(seed: String = "", qrBitmap: Bitmap? = null, copySeed:(
) )
} }
AnimatedVisibility(showQr.value, modifier = Modifier.align(Alignment.CenterHorizontally)) { AnimatedVisibility(
Card( showQr.value,
backgroundColor = LocalExtraColors.current.lightCell, modifier = Modifier.align(Alignment.CenterHorizontally).padding(vertical = 24.dp)
elevation = 0.dp, ) {
modifier = Modifier QrImageCard(
.align(Alignment.CenterHorizontally) seed,
.padding(vertical = 24.dp) contentDescription = "QR code of your recovery password",
) { icon = R.drawable.session_shield
qrBitmap?.let { )
QrImage(
bitmap = it,
contentDescription = "QR code of your recovery password",
)
}
}
} }
AnimatedVisibility(!showQr.value) { AnimatedVisibility(!showQr.value) {
@ -205,29 +189,6 @@ fun RecoveryPasswordCell(seed: String = "", qrBitmap: Bitmap? = null, copySeed:(
} }
} }
@Composable
fun QrImage(bitmap: Bitmap, contentDescription: String, icon: Int = R.drawable.session_shield) {
Box {
Image(
bitmap = bitmap.asImageBitmap(),
contentDescription = contentDescription,
colorFilter = ColorFilter.tint(LocalExtraColors.current.onLightCell)
)
Icon(
painter = painterResource(id = icon),
contentDescription = "",
tint = LocalExtraColors.current.onLightCell,
modifier = Modifier
.align(Alignment.Center)
.width(46.dp)
.height(56.dp)
.background(color = LocalExtraColors.current.lightCell)
.padding(horizontal = 3.dp, vertical = 1.dp)
)
}
}
private fun MutableState<Boolean>.toggle() { value = !value } private fun MutableState<Boolean>.toggle() { value = !value }
@Composable @Composable
@ -247,7 +208,3 @@ fun HideRecoveryPasswordCell(onHide: () -> Unit = {}) {
} }
} }
} }
fun Context.startRecoveryPasswordActivity() {
Intent(this, RecoveryPasswordActivity::class.java).also(::startActivity)
}

View File

@ -42,13 +42,4 @@ class RecoveryPasswordViewModel @Inject constructor(
MnemonicCodec { MnemonicUtilities.loadFileContents(application, it) } MnemonicCodec { MnemonicUtilities.loadFileContents(application, it) }
.encode(hexEncodedSeed, MnemonicCodec.Language.Configuration.english) .encode(hexEncodedSeed, MnemonicCodec.Language.Configuration.english)
} }
val qrBitmap by lazy {
QRCodeUtilities.encode(
data = seed,
size = toPx(280, application.resources),
isInverted = false,
hasTransparentBackground = true
)
}
} }

View File

@ -2,15 +2,14 @@ package org.thoughtcrime.securesms.preferences
import android.Manifest import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.BroadcastReceiver
import android.content.ClipData 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.content.IntentFilter
import android.net.Uri import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Parcelable import android.os.Parcelable
import android.util.SparseArray import android.util.SparseArray
import android.view.ActionMode import android.view.ActionMode
@ -20,9 +19,38 @@ import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import network.loki.messenger.BuildConfig import network.loki.messenger.BuildConfig
import network.loki.messenger.R import network.loki.messenger.R
import network.loki.messenger.databinding.ActivitySettingsBinding import network.loki.messenger.databinding.ActivitySettingsBinding
@ -34,23 +62,37 @@ import nl.komponents.kovenant.ui.successUi
import org.session.libsession.avatars.AvatarHelper import org.session.libsession.avatars.AvatarHelper
import org.session.libsession.avatars.ProfileContactPhoto import org.session.libsession.avatars.ProfileContactPhoto
import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.snode.SnodeAPI import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.* import org.session.libsession.utilities.Address
import org.session.libsession.utilities.ProfileKeyUtil
import org.session.libsession.utilities.ProfilePictureUtilities
import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsession.utilities.truncateIdForDisplay
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.avatar.AvatarSelection import org.thoughtcrime.securesms.avatar.AvatarSelection
import org.thoughtcrime.securesms.components.ProfilePictureView import org.thoughtcrime.securesms.components.ProfilePictureView
import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.home.PathActivity import org.thoughtcrime.securesms.home.PathActivity
import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity import org.thoughtcrime.securesms.messagerequests.MessageRequestsActivity
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.onboarding.recoverypassword.RecoveryPasswordActivity
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.onboarding.recoverypassword.startRecoveryPasswordActivity
import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity import org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
import org.thoughtcrime.securesms.showSessionDialog import org.thoughtcrime.securesms.showSessionDialog
import org.thoughtcrime.securesms.ui.AppTheme
import org.thoughtcrime.securesms.ui.BorderlessButton
import org.thoughtcrime.securesms.ui.Cell
import org.thoughtcrime.securesms.ui.Divider
import org.thoughtcrime.securesms.ui.ItemButton
import org.thoughtcrime.securesms.ui.ItemButtonWithDrawable
import org.thoughtcrime.securesms.ui.OutlineButton
import org.thoughtcrime.securesms.ui.PreviewTheme
import org.thoughtcrime.securesms.ui.ThemeResPreviewParameterProvider
import org.thoughtcrime.securesms.ui.destructiveButtonColors
import org.thoughtcrime.securesms.util.BitmapDecodingException import org.thoughtcrime.securesms.util.BitmapDecodingException
import org.thoughtcrime.securesms.util.BitmapUtil import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
@ -61,6 +103,8 @@ import java.io.File
import java.security.SecureRandom import java.security.SecureRandom
import javax.inject.Inject import javax.inject.Inject
private const val TAG = "SettingsActivity"
@AndroidEntryPoint @AndroidEntryPoint
class SettingsActivity : PassphraseRequiredActionBarActivity() { class SettingsActivity : PassphraseRequiredActionBarActivity() {
@ -69,21 +113,14 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
@Inject @Inject
lateinit var prefs: TextSecurePreferences lateinit var prefs: TextSecurePreferences
private lateinit var binding: ActivitySettingsBinding private lateinit var binding: ActivitySettingsBinding
private var displayNameEditActionMode: ActionMode? = null private var displayNameEditActionMode: ActionMode? = null
set(value) { field = value; handleDisplayNameEditActionModeChanged() } set(value) { field = value; handleDisplayNameEditActionModeChanged() }
private lateinit var glide: GlideRequests
private var tempFile: File? = null private var tempFile: File? = null
private val hexEncodedPublicKey: String private val hexEncodedPublicKey: String get() = TextSecurePreferences.getLocalNumber(this)!!
get() {
return TextSecurePreferences.getLocalNumber(this)!!
}
companion object { companion object {
const val updatedProfileResultCode = 1234
private const val SCROLL_STATE = "SCROLL_STATE" private const val SCROLL_STATE = "SCROLL_STATE"
} }
@ -92,7 +129,12 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
super.onCreate(savedInstanceState, isReady) super.onCreate(savedInstanceState, isReady)
binding = ActivitySettingsBinding.inflate(layoutInflater) binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
glide = GlideApp.with(this)
binding.composeView.setContent {
AppTheme {
Buttons()
}
}
} }
override fun onStart() { override fun onStart() {
@ -104,21 +146,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) } ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) }
btnGroupNameDisplay.text = getDisplayName() btnGroupNameDisplay.text = getDisplayName()
publicKeyTextView.text = hexEncodedPublicKey publicKeyTextView.text = hexEncodedPublicKey
copyButton.setOnClickListener { copyPublicKey() }
shareButton.setOnClickListener { sharePublicKey() }
pathButton.setOnClickListener { showPath() }
pathContainer.disableClipping()
privacyButton.setOnClickListener { showPrivacySettings() }
notificationsButton.setOnClickListener { showNotificationSettings() }
messageRequestsButton.setOnClickListener { showMessageRequests() }
chatsButton.setOnClickListener { showChatSettings() }
appearanceButton.setOnClickListener { showAppearanceSettings() }
inviteFriendButton.setOnClickListener { sendInvitation() }
helpButton.setOnClickListener { showHelp() }
passwordDivider.isGone = prefs.getHidePassword()
passwordButton.isGone = prefs.getHidePassword()
passwordButton.setOnClickListener { showPassword() }
clearAllDataButton.setOnClickListener { clearAllData() }
versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})")
} }
} }
@ -167,30 +194,22 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
@Deprecated("Deprecated in Java") @Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
if (resultCode != Activity.RESULT_OK) return
when (requestCode) { when (requestCode) {
AvatarSelection.REQUEST_CODE_AVATAR -> { AvatarSelection.REQUEST_CODE_AVATAR -> {
if (resultCode != Activity.RESULT_OK) {
return
}
val outputFile = Uri.fromFile(File(cacheDir, "cropped")) val outputFile = Uri.fromFile(File(cacheDir, "cropped"))
var inputFile: Uri? = data?.data val inputFile: Uri? = data?.data ?: tempFile?.let(Uri::fromFile)
if (inputFile == null && tempFile != null) {
inputFile = Uri.fromFile(tempFile)
}
AvatarSelection.circularCropImage(this, inputFile, outputFile, R.string.CropImageActivity_profile_avatar) AvatarSelection.circularCropImage(this, inputFile, outputFile, R.string.CropImageActivity_profile_avatar)
} }
AvatarSelection.REQUEST_CODE_CROP_IMAGE -> { AvatarSelection.REQUEST_CODE_CROP_IMAGE -> {
if (resultCode != Activity.RESULT_OK) { lifecycleScope.launch(Dispatchers.IO) {
return
}
AsyncTask.execute {
try { try {
val profilePictureToBeUploaded = BitmapUtil.createScaledBytes(this@SettingsActivity, AvatarSelection.getResultUri(data), ProfileMediaConstraints()).bitmap val profilePictureToBeUploaded = BitmapUtil.createScaledBytes(this@SettingsActivity, AvatarSelection.getResultUri(data), ProfileMediaConstraints()).bitmap
Handler(Looper.getMainLooper()).post { launch(Dispatchers.Main) {
updateProfile(true, profilePictureToBeUploaded) updateProfile(true, profilePictureToBeUploaded)
} }
} catch (e: BitmapDecodingException) { } catch (e: BitmapDecodingException) {
e.printStackTrace() Log.e(TAG, e)
} }
} }
} }
@ -205,10 +224,10 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
// region Updating // region Updating
private fun handleDisplayNameEditActionModeChanged() { private fun handleDisplayNameEditActionModeChanged() {
val isEditingDisplayName = this.displayNameEditActionMode !== null val isEditingDisplayName = this.displayNameEditActionMode != null
binding.btnGroupNameDisplay.visibility = if (isEditingDisplayName) View.INVISIBLE else View.VISIBLE binding.btnGroupNameDisplay.isInvisible = isEditingDisplayName
binding.displayNameEditText.visibility = if (isEditingDisplayName) View.VISIBLE else View.INVISIBLE binding.displayNameEditText.isInvisible = !isEditingDisplayName
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (isEditingDisplayName) { if (isEditingDisplayName) {
@ -255,12 +274,11 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
MessagingModuleConfiguration.shared.storage.clearUserPic() MessagingModuleConfiguration.shared.storage.clearUserPic()
} }
} }
val compoundPromise = all(promises) all(promises) successUi { // Do this on the UI thread so that it happens before the alwaysUi clause below
compoundPromise.successUi { // Do this on the UI thread so that it happens before the alwaysUi clause below
val userConfig = configFactory.user val userConfig = configFactory.user
if (isUpdatingProfilePicture) { if (isUpdatingProfilePicture) {
AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture) AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture)
TextSecurePreferences.setProfileAvatarId(this, profilePicture?.let { SecureRandom().nextInt() } ?: 0 ) prefs.setProfileAvatarId(profilePicture?.let { SecureRandom().nextInt() } ?: 0 )
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey) ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
// new config // new config
val url = TextSecurePreferences.getProfilePictureURL(this) val url = TextSecurePreferences.getProfilePictureURL(this)
@ -275,8 +293,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
configFactory.persist(userConfig, SnodeAPI.nowWithOffset) configFactory.persist(userConfig, SnodeAPI.nowWithOffset)
} }
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity) ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
} } alwaysUi {
compoundPromise.alwaysUi {
if (displayName != null) { if (displayName != null) {
binding.btnGroupNameDisplay.text = displayName binding.btnGroupNameDisplay.text = displayName
} }
@ -318,23 +335,23 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
title(R.string.activity_settings_set_display_picture) title(R.string.activity_settings_set_display_picture)
view(R.layout.dialog_change_avatar) view(R.layout.dialog_change_avatar)
button(R.string.activity_settings_upload) { startAvatarSelection() } button(R.string.activity_settings_upload) { startAvatarSelection() }
if (TextSecurePreferences.getProfileAvatarId(context) != 0) { if (prefs.getProfileAvatarId() != 0) {
button(R.string.activity_settings_remove) { removeAvatar() } button(R.string.activity_settings_remove) { removeAvatar() }
} }
cancelButton() cancelButton()
}.apply { }.apply {
val profilePic = findViewById<ProfilePictureView>(R.id.profile_picture_view) val profilePic = findViewById<ProfilePictureView>(R.id.profile_picture_view)
?.also(::setupProfilePictureView) ?.also(::setupProfilePictureView)
val pictureIcon = findViewById<View>(R.id.ic_pictures) val pictureIcon = findViewById<View>(R.id.ic_pictures)
val recipient = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false) val recipient = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false)
val photoSet = (recipient.contactPhoto as ProfileContactPhoto).avatarObject !in setOf("0", "") val photoSet = (recipient.contactPhoto as ProfileContactPhoto).avatarObject !in setOf("0", "")
profilePic?.isVisible = photoSet profilePic?.isVisible = photoSet
pictureIcon?.isVisible = !photoSet pictureIcon?.isVisible = !photoSet
} }
} }
private fun removeAvatar() { private fun removeAvatar() {
@ -359,65 +376,21 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
} }
private fun sharePublicKey() { private fun sharePublicKey() {
val intent = Intent() Intent().apply {
intent.action = Intent.ACTION_SEND action = Intent.ACTION_SEND
intent.putExtra(Intent.EXTRA_TEXT, hexEncodedPublicKey) putExtra(Intent.EXTRA_TEXT, hexEncodedPublicKey)
intent.type = "text/plain" type = "text/plain"
val chooser = Intent.createChooser(intent, getString(R.string.share)) }.let { Intent.createChooser(it, getString(R.string.share)) }
startActivity(chooser) .let(::startActivity)
}
private fun showPrivacySettings() {
val intent = Intent(this, PrivacySettingsActivity::class.java)
push(intent)
}
private fun showNotificationSettings() {
val intent = Intent(this, NotificationSettingsActivity::class.java)
push(intent)
}
private fun showMessageRequests() {
val intent = Intent(this, MessageRequestsActivity::class.java)
push(intent)
}
private fun showChatSettings() {
val intent = Intent(this, ChatSettingsActivity::class.java)
push(intent)
}
private fun showAppearanceSettings() {
val intent = Intent(this, AppearanceSettingsActivity::class.java)
push(intent)
} }
private fun sendInvitation() { private fun sendInvitation() {
val intent = Intent() Intent().apply {
intent.action = Intent.ACTION_SEND action = Intent.ACTION_SEND
val invitation = "Hey, I've been using Session to chat with complete privacy and security. Come join me! Download it at https://getsession.org/. My Session ID is $hexEncodedPublicKey !" putExtra(Intent.EXTRA_TEXT, "Hey, I've been using Session to chat with complete privacy and security. Come join me! Download it at https://getsession.org/. My Session ID is $hexEncodedPublicKey !")
intent.putExtra(Intent.EXTRA_TEXT, invitation) type = "text/plain"
intent.type = "text/plain" }.let { Intent.createChooser(it, getString(R.string.activity_settings_invite_button_title)) }
val chooser = Intent.createChooser(intent, getString(R.string.activity_settings_invite_button_title)) .let(::startActivity)
startActivity(chooser)
}
private fun showHelp() {
val intent = Intent(this, HelpSettingsActivity::class.java)
push(intent)
}
private fun showPath() {
val intent = Intent(this, PathActivity::class.java)
show(intent)
}
private fun showPassword() {
startRecoveryPasswordActivity()
}
private fun clearAllData() {
ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog")
} }
// endregion // endregion
@ -451,4 +424,88 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
return false; return false;
} }
} }
@Composable
fun Buttons() {
Column {
Row(
modifier = Modifier.padding(horizontal = 24.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
OutlineButton(
modifier = Modifier.weight(1f),
onClick = { sharePublicKey() }
) { Text(stringResource(R.string.share)) }
OutlineButton(
modifier = Modifier.weight(1f),
onClick = { copyPublicKey() },
temporaryContent = { Text(stringResource(R.string.copied)) }
) {
Text(stringResource(R.string.copy))
}
}
Spacer(modifier = Modifier.height(24.dp))
var hasPaths by remember {
mutableStateOf(false)
}
CheckPaths { hasPaths = it }
Cell {
Column {
ItemButtonWithDrawable(R.string.activity_path_title, icon = if (hasPaths) R.drawable.ic_status else R.drawable.ic_path_yellow) { show<PathActivity>() }
Divider()
ItemButton(R.string.activity_settings_privacy_button_title, icon = R.drawable.ic_privacy_icon) { show<PrivacySettingsActivity>() }
Divider()
ItemButton(R.string.activity_settings_notifications_button_title, icon = R.drawable.ic_speaker, contentDescription = R.string.AccessibilityId_notifications) { show<NotificationSettingsActivity>() }
Divider()
ItemButton(R.string.activity_settings_conversations_button_title, icon = R.drawable.ic_conversations, contentDescription = R.string.AccessibilityId_conversations) { show<ChatSettingsActivity>() }
Divider()
ItemButton(R.string.activity_settings_message_requests_button_title, icon = R.drawable.ic_message_requests, contentDescription = R.string.AccessibilityId_message_requests) { show<MessageRequestsActivity>() }
Divider()
ItemButton(R.string.activity_settings_message_appearance_button_title, icon = R.drawable.ic_appearance, contentDescription = R.string.AccessibilityId_appearance) { show<AppearanceSettingsActivity>() }
Divider()
ItemButton(R.string.activity_settings_invite_button_title, icon = R.drawable.ic_invite_friend, contentDescription = R.string.AccessibilityId_invite_friend) { sendInvitation() }
Divider()
if (!prefs.getHidePassword()) {
ItemButton(R.string.sessionRecoveryPassword, icon = R.drawable.ic_recovery_phrase, contentDescription = R.string.AccessibilityId_recovery_password_menu_item) { show<RecoveryPasswordActivity>() }
Divider()
}
ItemButton(R.string.activity_settings_help_button, icon = R.drawable.ic_help, contentDescription = R.string.AccessibilityId_help) { show<HelpSettingsActivity>() }
Divider()
ItemButton(R.string.activity_settings_clear_all_data_button_title, colors = destructiveButtonColors(), icon = R.drawable.ic_clear_data, contentDescription = R.string.AccessibilityId_clear_data) { ClearAllDataDialog().show(supportFragmentManager, "Clear All Data Dialog") }
}
}
}
}
@Composable
fun CheckPaths(setHasPaths: (Boolean) -> Unit) {
val context = LocalContext.current
val manager = LocalBroadcastManager.getInstance(context)
fun update() {
lifecycleScope.launch {
val paths = withContext(Dispatchers.IO) { OnionRequestAPI.paths }
setHasPaths(paths.isNotEmpty())
}
}
fun addReceiver(action: String): BroadcastReceiver = createReceiver { update() }.also { manager.registerReceiver(it, IntentFilter(action)) }
val receivers = listOf("buildingPaths", "pathsBuilt").map(::addReceiver)
DisposableEffect(Unit) {
onDispose {
receivers.forEach(manager::unregisterReceiver)
}
}
}
}
fun createReceiver(update: () -> Unit) = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { update() }
} }

View File

@ -1,13 +1,17 @@
package org.thoughtcrime.securesms.ui package org.thoughtcrime.securesms.ui
import android.graphics.drawable.BitmapDrawable
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.ScrollState import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
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.RowScope import androidx.compose.foundation.layout.RowScope
@ -23,6 +27,7 @@ import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonColors import androidx.compose.material.ButtonColors
import androidx.compose.material.ButtonDefaults import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Card import androidx.compose.material.Card
@ -52,10 +57,12 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@ -64,6 +71,8 @@ import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.res.ResourcesCompat
import com.google.accompanist.drawablepainter.rememberDrawablePainter
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -259,6 +268,42 @@ fun <T> OptionsCard(card: OptionsCard<T>, callbacks: Callbacks<T>) {
} }
@Composable
fun ItemButton(
@StringRes textId: Int,
@DrawableRes icon: Int,
colors: ButtonColors = transparentButtonColors(),
@StringRes contentDescription: Int = textId,
onClick: () -> Unit
) {
ItemButton(stringResource(textId), icon, colors, stringResource(contentDescription), onClick)
}
@Composable
fun ItemButtonWithDrawable(
@StringRes textId: Int,
@DrawableRes icon: Int,
colors: ButtonColors = transparentButtonColors(),
@StringRes contentDescription: Int = textId,
onClick: () -> Unit
) {
val context = LocalContext.current
ItemButton(
text = stringResource(textId),
icon = {
Image(
painter = rememberDrawablePainter(drawable = context.getDrawable(icon)),
contentDescription = stringResource(contentDescription),
modifier = Modifier.align(Alignment.Center)
)
},
colors = colors,
contentDescription = stringResource(contentDescription),
onClick = onClick
)
}
@Composable @Composable
fun ItemButton( fun ItemButton(
text: String, text: String,
@ -267,24 +312,43 @@ fun ItemButton(
contentDescription: String = text, contentDescription: String = text,
onClick: () -> Unit onClick: () -> Unit
) { ) {
TextButton( ItemButton(
modifier = Modifier text = text,
.fillMaxWidth() icon = {
.height(60.dp),
colors = colors,
onClick = onClick,
shape = RectangleShape,
) {
Box(modifier = Modifier
.width(80.dp)
.fillMaxHeight()) {
Icon( Icon(
painter = painterResource(id = icon), painter = painterResource(id = icon),
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = Modifier.align(Alignment.Center) modifier = Modifier.align(Alignment.Center)
) )
},
colors = colors,
contentDescription = contentDescription,
onClick = onClick
)
}
@Composable
fun ItemButton(
text: String,
icon: @Composable BoxScope.() -> Unit,
colors: ButtonColors = transparentButtonColors(),
contentDescription: String = text,
onClick: () -> Unit
) {
TextButton(
modifier = Modifier
.fillMaxWidth()
.height(60.dp),
colors = colors,
onClick = onClick,
shape = RectangleShape,
) {
Box(modifier = Modifier
.width(80.dp)
.fillMaxHeight()) {
icon()
} }
Text(text, modifier = Modifier.fillMaxWidth()) Text(text, modifier = Modifier.fillMaxWidth(), style = MaterialTheme.typography.h8)
} }
} }
@ -308,9 +372,9 @@ fun CellWithPaddingAndMargin(
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),
elevation = 0.dp, elevation = 0.dp,
modifier = Modifier modifier = Modifier
.wrapContentHeight() .wrapContentHeight()
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = margin), .padding(horizontal = margin),
) { ) {
Box(Modifier.padding(padding)) { content() } Box(Modifier.padding(padding)) { content() }
} }
@ -321,14 +385,14 @@ fun <T> TitledRadioButton(option: RadioOption<T>, onClick: () -> Unit) {
Row( Row(
horizontalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier modifier = Modifier
.runIf(option.enabled) { clickable { if (!option.selected) onClick() } } .runIf(option.enabled) { clickable { if (!option.selected) onClick() } }
.heightIn(min = 60.dp) .heightIn(min = 60.dp)
.padding(horizontal = 32.dp) .padding(horizontal = 32.dp)
.contentDescription(option.contentDescription) .contentDescription(option.contentDescription)
) { ) {
Column(modifier = Modifier Column(modifier = Modifier
.weight(1f) .weight(1f)
.align(Alignment.CenterVertically)) { .align(Alignment.CenterVertically)) {
Column { Column {
Text( Text(
text = option.title(), text = option.title(),
@ -349,8 +413,8 @@ fun <T> TitledRadioButton(option: RadioOption<T>, onClick: () -> Unit) {
onClick = null, onClick = null,
enabled = option.enabled, enabled = option.enabled,
modifier = Modifier modifier = Modifier
.height(26.dp) .height(26.dp)
.align(Alignment.CenterVertically) .align(Alignment.CenterVertically)
) )
} }
} }
@ -373,8 +437,8 @@ fun Modifier.contentDescription(id: Int?): Modifier {
fun OutlineButton(text: GetString, contentDescription: GetString? = text, modifier: Modifier = Modifier, onClick: () -> Unit) { fun OutlineButton(text: GetString, contentDescription: GetString? = text, modifier: Modifier = Modifier, onClick: () -> Unit) {
OutlinedButton( OutlinedButton(
modifier = modifier modifier = modifier
.size(108.dp, 34.dp) .size(108.dp, 34.dp)
.contentDescription(contentDescription), .contentDescription(contentDescription),
onClick = onClick, onClick = onClick,
border = BorderStroke(1.dp, LocalExtraColors.current.prominentButtonColor), border = BorderStroke(1.dp, LocalExtraColors.current.prominentButtonColor),
shape = RoundedCornerShape(50), // = 50% percent shape = RoundedCornerShape(50), // = 50% percent
@ -396,37 +460,37 @@ fun Modifier.fadingEdges(
topEdgeHeight: Dp = 0.dp, topEdgeHeight: Dp = 0.dp,
bottomEdgeHeight: Dp = 20.dp bottomEdgeHeight: Dp = 20.dp
): Modifier = this.then( ): Modifier = this.then(
Modifier Modifier
// adding layer fixes issue with blending gradient and content // adding layer fixes issue with blending gradient and content
.graphicsLayer { alpha = 0.99F } .graphicsLayer { alpha = 0.99F }
.drawWithContent { .drawWithContent {
drawContent() drawContent()
val topColors = listOf(Color.Transparent, Color.Black) val topColors = listOf(Color.Transparent, Color.Black)
val topStartY = scrollState.value.toFloat() val topStartY = scrollState.value.toFloat()
val topGradientHeight = min(topEdgeHeight.toPx(), topStartY) val topGradientHeight = min(topEdgeHeight.toPx(), topStartY)
if (topGradientHeight > 0f) drawRect( if (topGradientHeight > 0f) drawRect(
brush = Brush.verticalGradient( brush = Brush.verticalGradient(
colors = topColors, colors = topColors,
startY = topStartY, startY = topStartY,
endY = topStartY + topGradientHeight endY = topStartY + topGradientHeight
), ),
blendMode = BlendMode.DstIn blendMode = BlendMode.DstIn
) )
val bottomColors = listOf(Color.Black, Color.Transparent) val bottomColors = listOf(Color.Black, Color.Transparent)
val bottomEndY = size.height - scrollState.maxValue + scrollState.value val bottomEndY = size.height - scrollState.maxValue + scrollState.value
val bottomGradientHeight = val bottomGradientHeight =
min(bottomEdgeHeight.toPx(), scrollState.maxValue.toFloat() - scrollState.value) min(bottomEdgeHeight.toPx(), scrollState.maxValue.toFloat() - scrollState.value)
if (bottomGradientHeight > 0f) drawRect( if (bottomGradientHeight > 0f) drawRect(
brush = Brush.verticalGradient( brush = Brush.verticalGradient(
colors = bottomColors, colors = bottomColors,
startY = bottomEndY - bottomGradientHeight, startY = bottomEndY - bottomGradientHeight,
endY = bottomEndY endY = bottomEndY
), ),
blendMode = BlendMode.DstIn blendMode = BlendMode.DstIn
) )
} }
) )
@Composable @Composable
@ -440,16 +504,16 @@ fun Divider() {
fun RowScope.Avatar(recipient: Recipient) { fun RowScope.Avatar(recipient: Recipient) {
Box( Box(
modifier = Modifier modifier = Modifier
.width(60.dp) .width(60.dp)
.align(Alignment.CenterVertically) .align(Alignment.CenterVertically)
) { ) {
AndroidView( AndroidView(
factory = { factory = {
ProfilePictureView(it).apply { update(recipient) } ProfilePictureView(it).apply { update(recipient) }
}, },
modifier = Modifier modifier = Modifier
.width(46.dp) .width(46.dp)
.height(46.dp) .height(46.dp)
) )
} }
} }
@ -476,8 +540,8 @@ fun Arc(
) { ) {
Canvas( Canvas(
modifier = modifier modifier = modifier
.padding(strokeWidth) .padding(strokeWidth)
.size(186.dp) .size(186.dp)
) { ) {
// Background Line // Background Line
drawArc( drawArc(
@ -506,8 +570,8 @@ fun RowScope.SessionShieldIcon() {
painter = painterResource(R.drawable.session_shield), painter = painterResource(R.drawable.session_shield),
contentDescription = null, contentDescription = null,
modifier = Modifier modifier = Modifier
.align(Alignment.CenterVertically) .align(Alignment.CenterVertically)
.wrapContentSize(unbounded = true) .wrapContentSize(unbounded = true)
) )
} }

View File

@ -0,0 +1,79 @@
package org.thoughtcrime.securesms.ui.components
import android.graphics.Bitmap
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.LocalExtraColors
import org.thoughtcrime.securesms.util.QRCodeUtilities
@Composable
fun QrImageCard(
string: String,
contentDescription: String,
modifier: Modifier = Modifier,
icon: Int = R.drawable.session_shield
) {
Card(
backgroundColor = LocalExtraColors.current.lightCell,
elevation = 0.dp,
modifier = modifier
) { QrImage(string, contentDescription, icon) }
}
@Composable
fun QrImage(string: String, contentDescription: String, icon: Int = R.drawable.session_shield) {
var bitmap: Bitmap? by remember {
mutableStateOf(null)
}
val scope = rememberCoroutineScope()
LaunchedEffect(string) {
scope.launch(Dispatchers.IO) {
bitmap = QRCodeUtilities.encode(string, 400)
}
}
Box {
bitmap?.let {
Image(
bitmap = it.asImageBitmap(),
contentDescription = contentDescription,
colorFilter = ColorFilter.tint(LocalExtraColors.current.onLightCell)
)
}
Icon(
painter = painterResource(id = icon),
contentDescription = "",
tint = LocalExtraColors.current.onLightCell,
modifier = Modifier
.align(Alignment.Center)
.width(46.dp)
.height(56.dp)
.background(color = LocalExtraColors.current.lightCell)
.padding(horizontal = 3.dp, vertical = 1.dp)
)
}
}

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.util package org.thoughtcrime.securesms.util
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.view.View import android.view.View
@ -90,12 +91,17 @@ fun String.getThemeStyle(): Int = when (this) {
} }
@StyleRes @StyleRes
fun Int.getDefaultAccentColor(): Int = fun Int.getDefaultAccentColor(): Int = when (this) {
if (this == R.style.Ocean_Dark || this == R.style.Ocean_Light) R.style.PrimaryBlue R.style.Ocean_Dark, R.style.Ocean_Light -> R.style.PrimaryBlue
else R.style.PrimaryGreen else -> R.style.PrimaryGreen
}
data class ThemeState ( data class ThemeState (
@StyleRes val theme: Int, @StyleRes val theme: Int,
@StyleRes val accentStyle: Int, @StyleRes val accentStyle: Int,
val followSystem: Boolean val followSystem: Boolean
) )
inline fun <reified T: Activity> Context.start() = Intent(this, T::class.java).also(::startActivity)
inline fun <reified T: Activity> Activity.show() = Intent(this, T::class.java).also(::startActivity).also { overridePendingTransition(R.anim.slide_from_bottom, R.anim.fade_scale_out) }
inline fun <reified T: Activity> Activity.push() = Intent(this, T::class.java).also(::startActivity).also { overridePendingTransition(R.anim.slide_from_right, R.anim.fade_scale_out) }

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:width="28dp" android:height="28dp">
<shape android:shape="oval">
<gradient
android:type="radial"
android:startColor="#FFCE3A"
android:endColor="#00FFCE3A"
android:gradientRadius="12dp"/>
</shape>
</item>
<item android:top="6dp" android:left="6dp" android:right="6dp" android:bottom="6dp">
<shape android:shape="oval">
<solid android:color="#FFCE3A"/>
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:width="28dp" android:height="28dp">
<shape android:shape="oval">
<gradient
android:type="radial"
android:startColor="#31F196"
android:endColor="#0031F196"
android:gradientRadius="12dp"/>
</shape>
</item>
<item android:top="6dp" android:left="6dp" android:right="6dp" android:bottom="6dp">
<shape android:shape="oval">
<solid android:color="#31F196"/>
</shape>
</item>
</layer-list>

View File

@ -88,392 +88,11 @@
android:textAlignment="center" android:textAlignment="center"
android:contentDescription="@string/AccessibilityId_account_id" android:contentDescription="@string/AccessibilityId_account_id"
tools:text="05987d601943c267879be41830888066c6a024cbdc9a548d06813924bf3372ea78" /> tools:text="05987d601943c267879be41830888066c6a024cbdc9a548d06813924bf3372ea78" />
<LinearLayout <androidx.compose.ui.platform.ComposeView
android:id="@+id/composeView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"/>
android:layout_marginLeft="@dimen/large_spacing"
android:layout_marginTop="@dimen/large_spacing"
android:layout_marginRight="@dimen/large_spacing"
android:orientation="horizontal">
<Button
style="@style/Widget.Session.Button.Common.ProminentOutline"
android:id="@+id/copyButton"
android:layout_width="0dp"
android:layout_height="@dimen/medium_button_height"
android:layout_weight="1"
android:text="@string/copy" />
<Button
style="@style/Widget.Session.Button.Common.ProminentOutline"
android:id="@+id/shareButton"
android:layout_width="0dp"
android:layout_height="@dimen/medium_button_height"
android:layout_weight="1"
android:layout_marginStart="@dimen/medium_spacing"
android:text="@string/share" />
</LinearLayout>
<LinearLayout
android:background="@drawable/preference_single_no_padding"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/large_spacing"
android:layout_marginHorizontal="@dimen/large_spacing"
android:orientation="vertical">
<!-- Path -->
<RelativeLayout
android:id="@+id/pathButton"
android:background="?selectableItemBackground"
android:orientation="horizontal"
android:layout_width="match_parent"
android:paddingHorizontal="@dimen/large_spacing"
android:layout_height="@dimen/setting_button_height">
<FrameLayout
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:id="@+id/pathContainer"
android:layout_width="@dimen/small_profile_picture_size"
android:layout_height="@dimen/small_profile_picture_size">
<org.thoughtcrime.securesms.home.PathStatusView
android:layout_gravity="center"
android:layout_width="@dimen/path_status_view_size"
android:layout_height="@dimen/path_status_view_size"/>
</FrameLayout>
<TextView
android:id="@+id/pathText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_font_size"
android:layout_marginHorizontal="@dimen/medium_spacing"
android:textStyle="bold"
android:gravity="center"
android:layout_toEndOf="@+id/pathContainer"
android:text="@string/activity_path_title" />
</RelativeLayout>
<View
android:layout_marginHorizontal="@dimen/very_large_spacing"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?colorDividerBackground" />
<RelativeLayout
android:id="@+id/privacyButton"
android:background="?selectableItemBackground"
android:paddingHorizontal="@dimen/large_spacing"
android:layout_width="match_parent"
android:layout_height="@dimen/setting_button_height">
<ImageView
android:id="@+id/privacyContainer"
android:layout_width="@dimen/small_profile_picture_size"
android:layout_height="@dimen/small_profile_picture_size"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_privacy_icon"
android:scaleType="centerInside"
app:tint="?android:textColorPrimary" />
<TextView
android:id="@+id/privacyText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_font_size"
android:layout_marginHorizontal="@dimen/medium_spacing"
android:textStyle="bold"
android:gravity="center"
android:layout_toEndOf="@+id/privacyContainer"
android:text="@string/activity_settings_privacy_button_title" />
</RelativeLayout>
<View
android:layout_marginHorizontal="@dimen/very_large_spacing"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?colorDividerBackground" />
<RelativeLayout
android:id="@+id/notificationsButton"
android:background="?selectableItemBackground"
android:paddingHorizontal="@dimen/large_spacing"
android:layout_width="match_parent"
android:layout_height="@dimen/setting_button_height"
android:contentDescription="@string/AccessibilityId_notifications">
<ImageView
android:id="@+id/notificationsContainer"
android:layout_width="@dimen/small_profile_picture_size"
android:layout_height="@dimen/small_profile_picture_size"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_speaker"
android:scaleType="centerInside"
app:tint="?android:textColorPrimary" />
<TextView
android:id="@+id/notificationsText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_font_size"
android:layout_marginHorizontal="@dimen/medium_spacing"
android:textStyle="bold"
android:gravity="center"
android:layout_toEndOf="@+id/notificationsContainer"
android:text="@string/activity_settings_notifications_button_title"
/>
</RelativeLayout>
<View
android:layout_marginHorizontal="@dimen/very_large_spacing"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?colorDividerBackground" />
<RelativeLayout
android:id="@+id/chatsButton"
android:background="?selectableItemBackground"
android:paddingHorizontal="@dimen/large_spacing"
android:layout_width="match_parent"
android:layout_height="@dimen/setting_button_height"
android:contentDescription="@string/AccessibilityId_conversations">
<ImageView
android:id="@+id/chatsContainer"
android:layout_width="@dimen/small_profile_picture_size"
android:layout_height="@dimen/small_profile_picture_size"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_conversations"
android:scaleType="centerInside"
app:tint="?android:textColorPrimary" />
<TextView
android:id="@+id/chatsText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_font_size"
android:layout_marginHorizontal="@dimen/medium_spacing"
android:textStyle="bold"
android:gravity="center"
android:layout_toEndOf="@+id/chatsContainer"
android:text="@string/activity_settings_conversations_button_title" />
</RelativeLayout>
<View
android:layout_marginHorizontal="@dimen/very_large_spacing"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?colorDividerBackground" />
<RelativeLayout
android:id="@+id/messageRequestsButton"
android:background="?selectableItemBackground"
android:paddingHorizontal="@dimen/large_spacing"
android:layout_width="match_parent"
android:layout_height="@dimen/setting_button_height"
android:contentDescription="@string/AccessibilityId_message_requests">
<ImageView
android:id="@+id/messageRequestsContainer"
android:layout_width="@dimen/small_profile_picture_size"
android:layout_height="@dimen/small_profile_picture_size"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_message_requests"
android:scaleType="centerInside"
app:tint="?android:textColorPrimary" />
<TextView
android:id="@+id/messageRequestsTexts"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_font_size"
android:layout_marginHorizontal="@dimen/medium_spacing"
android:textStyle="bold"
android:gravity="center"
android:layout_toEndOf="@+id/messageRequestsContainer"
android:text="@string/activity_settings_message_requests_button_title" />
</RelativeLayout>
<View
android:layout_marginHorizontal="@dimen/very_large_spacing"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?colorDividerBackground" />
<RelativeLayout
android:id="@+id/appearanceButton"
android:background="?selectableItemBackground"
android:paddingHorizontal="@dimen/large_spacing"
android:layout_width="match_parent"
android:layout_height="@dimen/setting_button_height"
android:contentDescription="@string/AccessibilityId_appearance">
<ImageView
android:id="@+id/appearanceContainer"
android:layout_width="@dimen/small_profile_picture_size"
android:layout_height="@dimen/small_profile_picture_size"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_appearance"
android:scaleType="centerInside"
app:tint="?android:textColorPrimary" />
<TextView
android:id="@+id/appearanceText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_font_size"
android:layout_marginHorizontal="@dimen/medium_spacing"
android:textStyle="bold"
android:gravity="center"
android:layout_toEndOf="@+id/appearanceContainer"
android:text="@string/activity_settings_message_appearance_button_title" />
</RelativeLayout>
<View
android:layout_marginHorizontal="@dimen/very_large_spacing"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?colorDividerBackground" />
<RelativeLayout
android:id="@+id/inviteFriendButton"
android:background="?selectableItemBackground"
android:paddingHorizontal="@dimen/large_spacing"
android:layout_width="match_parent"
android:layout_height="@dimen/setting_button_height"
android:contentDescription="@string/AccessibilityId_invite_friend">
<ImageView
android:id="@+id/inviteFriendContainer"
android:layout_width="@dimen/small_profile_picture_size"
android:layout_height="@dimen/small_profile_picture_size"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_invite_friend"
android:scaleType="centerInside"
app:tint="?android:textColorPrimary" />
<TextView
android:id="@+id/inviteFriendTexts"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_font_size"
android:layout_marginHorizontal="@dimen/medium_spacing"
android:textStyle="bold"
android:gravity="center"
android:layout_toEndOf="@+id/inviteFriendContainer"
android:text="@string/activity_settings_invite_button_title" />
</RelativeLayout>
<View
android:layout_marginHorizontal="@dimen/very_large_spacing"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?colorDividerBackground" />
<RelativeLayout
android:id="@+id/passwordButton"
android:background="?selectableItemBackground"
android:paddingHorizontal="@dimen/large_spacing"
android:layout_width="match_parent"
android:layout_height="@dimen/setting_button_height"
android:contentDescription="@string/AccessibilityId_recovery_password_menu_item">
<ImageView
android:id="@+id/passwordContainer"
android:layout_width="@dimen/small_profile_picture_size"
android:layout_height="@dimen/small_profile_picture_size"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_recovery_phrase"
android:scaleType="centerInside"
app:tint="?android:textColorPrimary" />
<TextView
android:id="@+id/seedText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_font_size"
android:layout_marginHorizontal="@dimen/medium_spacing"
android:textStyle="bold"
android:gravity="center"
android:layout_toEndOf="@+id/passwordContainer"
android:text="@string/sessionRecoveryPassword" />
</RelativeLayout>
<View
android:id="@+id/passwordDivider"
android:layout_marginHorizontal="@dimen/very_large_spacing"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?colorDividerBackground" />
<RelativeLayout
android:id="@+id/helpButton"
android:background="?selectableItemBackground"
android:paddingHorizontal="@dimen/large_spacing"
android:layout_width="match_parent"
android:layout_height="@dimen/setting_button_height"
android:contentDescription="@string/AccessibilityId_help">
<ImageView
android:id="@+id/helpContainer"
android:layout_width="@dimen/small_profile_picture_size"
android:layout_height="@dimen/small_profile_picture_size"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_help"
android:scaleType="centerInside"
app:tint="?android:textColorPrimary" />
<TextView
android:id="@+id/helpTexts"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/medium_font_size"
android:layout_marginHorizontal="@dimen/medium_spacing"
android:textStyle="bold"
android:gravity="center"
android:layout_toEndOf="@+id/helpContainer"
android:text="@string/activity_settings_help_button" />
</RelativeLayout>
<View
android:layout_marginHorizontal="@dimen/very_large_spacing"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?colorDividerBackground" />
<RelativeLayout
android:id="@+id/clearAllDataButton"
android:background="?selectableItemBackground"
android:paddingHorizontal="@dimen/large_spacing"
android:layout_width="match_parent"
android:layout_height="@dimen/setting_button_height"
android:contentDescription="@string/AccessibilityId_clear_data">
<ImageView
android:id="@+id/clearContainer"
android:layout_width="@dimen/small_profile_picture_size"
android:layout_height="@dimen/small_profile_picture_size"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_clear_data"
android:scaleType="centerInside"/>
<TextView
android:id="@+id/clearText"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="@color/destructive"
android:textSize="@dimen/medium_font_size"
android:layout_marginHorizontal="@dimen/medium_spacing"
android:textStyle="bold"
android:gravity="center"
android:layout_toEndOf="@+id/clearContainer"
android:text="@string/activity_settings_clear_all_data_button_title" />
</RelativeLayout>
</LinearLayout>
<ImageView <ImageView
android:id="@+id/oxenLogoImageView" android:id="@+id/oxenLogoImageView"