mirror of
https://github.com/oxen-io/session-android.git
synced 2025-04-08 05:44:29 +00:00
WIP for new avatar selection
This commit is contained in:
parent
2174976716
commit
c38efc2ef8
@ -74,8 +74,9 @@ class AvatarSelection(
|
|||||||
*/
|
*/
|
||||||
fun startAvatarSelection(
|
fun startAvatarSelection(
|
||||||
includeClear: Boolean,
|
includeClear: Boolean,
|
||||||
attemptToIncludeCamera: Boolean
|
attemptToIncludeCamera: Boolean,
|
||||||
): File? {
|
createTempFile: ()->File?
|
||||||
|
) {
|
||||||
var captureFile: File? = null
|
var captureFile: File? = null
|
||||||
val hasCameraPermission = ContextCompat
|
val hasCameraPermission = ContextCompat
|
||||||
.checkSelfPermission(
|
.checkSelfPermission(
|
||||||
@ -83,18 +84,11 @@ class AvatarSelection(
|
|||||||
Manifest.permission.CAMERA
|
Manifest.permission.CAMERA
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
if (attemptToIncludeCamera && hasCameraPermission) {
|
if (attemptToIncludeCamera && hasCameraPermission) {
|
||||||
try {
|
captureFile = createTempFile()
|
||||||
captureFile = File.createTempFile("avatar-capture", ".jpg", getImageDir(activity))
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.e("Cannot reserve a temporary avatar capture file.", e)
|
|
||||||
} catch (e: NoExternalStorageException) {
|
|
||||||
Log.e("Cannot reserve a temporary avatar capture file.", e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val chooserIntent = createAvatarSelectionIntent(activity, captureFile, includeClear)
|
val chooserIntent = createAvatarSelectionIntent(activity, captureFile, includeClear)
|
||||||
onPickImage.launch(chooserIntent)
|
onPickImage.launch(chooserIntent)
|
||||||
return captureFile
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAvatarSelectionIntent(
|
private fun createAvatarSelectionIntent(
|
||||||
|
@ -572,7 +572,7 @@ class ConversationReactionOverlay : FrameLayout {
|
|||||||
items += ActionItem(R.attr.menu_save_icon,
|
items += ActionItem(R.attr.menu_save_icon,
|
||||||
R.string.save,
|
R.string.save,
|
||||||
{ handleActionItemClicked(Action.DOWNLOAD) },
|
{ handleActionItemClicked(Action.DOWNLOAD) },
|
||||||
R.string.AccessibilityId_save
|
R.string.AccessibilityId_saveAttachment
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,11 @@ import androidx.compose.foundation.layout.FlowRow
|
|||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
@ -46,6 +48,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
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.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
|
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
|
||||||
@ -212,7 +215,15 @@ fun CellMetadata(
|
|||||||
senderInfo?.let {
|
senderInfo?.let {
|
||||||
TitledView(state.fromTitle) {
|
TitledView(state.fromTitle) {
|
||||||
Row {
|
Row {
|
||||||
sender?.let { Avatar(it) }
|
sender?.let {
|
||||||
|
Avatar(
|
||||||
|
recipient = it,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
.size(46.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(LocalDimensions.current.smallSpacing))
|
||||||
|
}
|
||||||
TitledMonospaceText(it)
|
TitledMonospaceText(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
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.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@ -232,7 +231,7 @@ private fun SaveAttachmentWarningDialog(
|
|||||||
title = context.getString(R.string.warning),
|
title = context.getString(R.string.warning),
|
||||||
text = context.resources.getString(R.string.attachmentsWarning),
|
text = context.resources.getString(R.string.attachmentsWarning),
|
||||||
buttons = listOf(
|
buttons = listOf(
|
||||||
DialogButtonModel(GetString(R.string.save), GetString(R.string.AccessibilityId_save), color = LocalColors.current.danger, onClick = onAccepted),
|
DialogButtonModel(GetString(R.string.save), GetString(R.string.AccessibilityId_saveAttachment), color = LocalColors.current.danger, onClick = onAccepted),
|
||||||
DialogButtonModel(GetString(android.R.string.cancel), GetString(R.string.AccessibilityId_cancel), dismissOnClick = true)
|
DialogButtonModel(GetString(android.R.string.cancel), GetString(R.string.AccessibilityId_cancel), dismissOnClick = true)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -6,6 +6,7 @@ import android.content.BroadcastReceiver
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
@ -13,38 +14,52 @@ import android.util.SparseArray
|
|||||||
import android.view.ActionMode
|
import android.view.ActionMode
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
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.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
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.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
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.PreviewParameter
|
||||||
import androidx.core.view.isInvisible
|
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 androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import com.canhub.cropper.CropImage
|
|
||||||
import com.canhub.cropper.CropImageContract
|
import com.canhub.cropper.CropImageContract
|
||||||
import com.squareup.phrase.Phrase
|
import com.squareup.phrase.Phrase
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
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
|
||||||
@ -53,7 +68,6 @@ import nl.komponents.kovenant.ui.alwaysUi
|
|||||||
import nl.komponents.kovenant.ui.failUi
|
import nl.komponents.kovenant.ui.failUi
|
||||||
import nl.komponents.kovenant.ui.successUi
|
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.messaging.MessagingModuleConfiguration
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
@ -63,34 +77,36 @@ 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.StringSubstitutionConstants.VERSION_KEY
|
import org.session.libsession.utilities.StringSubstitutionConstants.VERSION_KEY
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
|
||||||
import org.session.libsession.utilities.truncateIdForDisplay
|
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||||
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.debugmenu.DebugActivity
|
import org.thoughtcrime.securesms.debugmenu.DebugActivity
|
||||||
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.permissions.Permissions
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
|
import org.thoughtcrime.securesms.preferences.SettingsViewModel.AvatarDialogState.*
|
||||||
import org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity
|
import org.thoughtcrime.securesms.preferences.appearance.AppearanceSettingsActivity
|
||||||
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
|
|
||||||
import org.thoughtcrime.securesms.recoverypassword.RecoveryPasswordActivity
|
import org.thoughtcrime.securesms.recoverypassword.RecoveryPasswordActivity
|
||||||
import org.thoughtcrime.securesms.showSessionDialog
|
import org.thoughtcrime.securesms.ui.AlertDialog
|
||||||
|
import org.thoughtcrime.securesms.ui.Avatar
|
||||||
import org.thoughtcrime.securesms.ui.Cell
|
import org.thoughtcrime.securesms.ui.Cell
|
||||||
|
import org.thoughtcrime.securesms.ui.DialogButtonModel
|
||||||
import org.thoughtcrime.securesms.ui.Divider
|
import org.thoughtcrime.securesms.ui.Divider
|
||||||
|
import org.thoughtcrime.securesms.ui.GetString
|
||||||
import org.thoughtcrime.securesms.ui.LargeItemButton
|
import org.thoughtcrime.securesms.ui.LargeItemButton
|
||||||
import org.thoughtcrime.securesms.ui.LargeItemButtonWithDrawable
|
import org.thoughtcrime.securesms.ui.LargeItemButtonWithDrawable
|
||||||
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
|
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
|
||||||
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineCopyButton
|
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineCopyButton
|
||||||
import org.thoughtcrime.securesms.ui.contentDescription
|
import org.thoughtcrime.securesms.ui.contentDescription
|
||||||
import org.thoughtcrime.securesms.ui.setThemedContent
|
import org.thoughtcrime.securesms.ui.setThemedContent
|
||||||
|
import org.thoughtcrime.securesms.ui.theme.LocalColors
|
||||||
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
||||||
|
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
|
||||||
|
import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider
|
||||||
|
import org.thoughtcrime.securesms.ui.theme.ThemeColors
|
||||||
import org.thoughtcrime.securesms.ui.theme.dangerButtonColors
|
import org.thoughtcrime.securesms.ui.theme.dangerButtonColors
|
||||||
import org.thoughtcrime.securesms.util.BitmapDecodingException
|
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
|
||||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||||
import org.thoughtcrime.securesms.util.NetworkUtils
|
import org.thoughtcrime.securesms.util.NetworkUtils
|
||||||
import org.thoughtcrime.securesms.util.push
|
import org.thoughtcrime.securesms.util.push
|
||||||
@ -106,41 +122,14 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var prefs: TextSecurePreferences
|
lateinit var prefs: TextSecurePreferences
|
||||||
|
|
||||||
|
private val viewModel: SettingsViewModel by viewModels()
|
||||||
|
|
||||||
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 var tempFile: File? = null
|
|
||||||
|
|
||||||
private val hexEncodedPublicKey: String get() = TextSecurePreferences.getLocalNumber(this)!!
|
|
||||||
|
|
||||||
private val onAvatarCropped = registerForActivityResult(CropImageContract()) { result ->
|
private val onAvatarCropped = registerForActivityResult(CropImageContract()) { result ->
|
||||||
when {
|
viewModel.onAvatarPicked(result)
|
||||||
result.isSuccessful -> {
|
|
||||||
Log.i(TAG, result.getUriFilePath(this).toString())
|
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
val profilePictureToBeUploaded =
|
|
||||||
BitmapUtil.createScaledBytes(
|
|
||||||
this@SettingsActivity,
|
|
||||||
result.getUriFilePath(this@SettingsActivity).toString(),
|
|
||||||
ProfileMediaConstraints()
|
|
||||||
).bitmap
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
updateProfilePicture(profilePictureToBeUploaded)
|
|
||||||
}
|
|
||||||
} catch (e: BitmapDecodingException) {
|
|
||||||
Log.e(TAG, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result is CropImage.CancelledResult -> {
|
|
||||||
Log.i(TAG, "Cropping image was cancelled by the user")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
Log.e(TAG, "Cropping image failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val onPickImage = registerForActivityResult(
|
private val onPickImage = registerForActivityResult(
|
||||||
@ -149,12 +138,14 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
|
||||||
|
|
||||||
val outputFile = Uri.fromFile(File(cacheDir, "cropped"))
|
val outputFile = Uri.fromFile(File(cacheDir, "cropped"))
|
||||||
val inputFile: Uri? = result.data?.data ?: tempFile?.let(Uri::fromFile)
|
val inputFile: Uri? = result.data?.data ?: viewModel.getTempFile()?.let(Uri::fromFile)
|
||||||
cropImage(inputFile, outputFile)
|
cropImage(inputFile, outputFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val avatarSelection = AvatarSelection(this, onAvatarCropped, onPickImage)
|
private val avatarSelection = AvatarSelection(this, onAvatarCropped, onPickImage)
|
||||||
|
|
||||||
|
private var showAvatarDialog: Boolean by mutableStateOf(false)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SCROLL_STATE = "SCROLL_STATE"
|
private const val SCROLL_STATE = "SCROLL_STATE"
|
||||||
}
|
}
|
||||||
@ -167,17 +158,37 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
|
|
||||||
// set the toolbar icon to a close icon
|
// set the toolbar icon to a close icon
|
||||||
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_baseline_close_24)
|
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_baseline_close_24)
|
||||||
|
|
||||||
|
// set the compose dialog content
|
||||||
|
binding.avatarDialog.setThemedContent {
|
||||||
|
if(showAvatarDialog){
|
||||||
|
AvatarDialogContainer(
|
||||||
|
saveAvatar = {
|
||||||
|
//todo TEMPORARY !!!!!!!!!!!!!!!!!!!!
|
||||||
|
(viewModel.avatarDialogState.value as? TempAvatar)?.let{ updateProfilePicture(it.data) }
|
||||||
|
},
|
||||||
|
removeAvatar = ::removeProfilePicture,
|
||||||
|
startAvatarSelection = ::startAvatarSelection
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
|
|
||||||
binding.run {
|
binding.run {
|
||||||
setupProfilePictureView(profilePictureView)
|
profilePictureView.apply {
|
||||||
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
|
publicKey = viewModel.hexEncodedPublicKey
|
||||||
|
displayName = viewModel.getDisplayName()
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
profilePictureView.setOnClickListener {
|
||||||
|
showAvatarDialog = true
|
||||||
|
}
|
||||||
ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) }
|
ctnGroupNameSection.setOnClickListener { startActionMode(DisplayNameEditActionModeCallback()) }
|
||||||
btnGroupNameDisplay.text = getDisplayName()
|
btnGroupNameDisplay.text = viewModel.getDisplayName()
|
||||||
publicKeyTextView.text = hexEncodedPublicKey
|
publicKeyTextView.text = viewModel.hexEncodedPublicKey
|
||||||
val gitCommitFirstSixChars = BuildConfig.GIT_HASH.take(6)
|
val gitCommitFirstSixChars = BuildConfig.GIT_HASH.take(6)
|
||||||
val environment: String = if(BuildConfig.BUILD_TYPE == "release") "" else " - ${prefs.getEnvironment().label}"
|
val environment: String = if(BuildConfig.BUILD_TYPE == "release") "" else " - ${prefs.getEnvironment().label}"
|
||||||
val versionDetails = " ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE} - $gitCommitFirstSixChars) $environment"
|
val versionDetails = " ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE} - $gitCommitFirstSixChars) $environment"
|
||||||
@ -195,17 +206,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_bottom)
|
overridePendingTransition(R.anim.fade_scale_in, R.anim.slide_to_bottom)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDisplayName(): String =
|
|
||||||
TextSecurePreferences.getProfileName(this) ?: truncateIdForDisplay(hexEncodedPublicKey)
|
|
||||||
|
|
||||||
private fun setupProfilePictureView(view: ProfilePictureView) {
|
|
||||||
view.apply {
|
|
||||||
publicKey = hexEncodedPublicKey
|
|
||||||
displayName = getDisplayName()
|
|
||||||
update()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
val scrollBundle = SparseArray<Parcelable>()
|
val scrollBundle = SparseArray<Parcelable>()
|
||||||
@ -310,6 +310,29 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
return updateWasSuccessful
|
return updateWasSuccessful
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// private fun createAvatarDialog(){
|
||||||
|
// if (avatarDialog != null) return
|
||||||
|
//
|
||||||
|
// avatarDialog = SettingsAvatarDialog(
|
||||||
|
// userKey = viewModel.hexEncodedPublicKey,
|
||||||
|
// userName = viewModel.getDisplayName(),
|
||||||
|
// startAvatarSelection = ::startAvatarSelection,
|
||||||
|
// saveAvatar = {
|
||||||
|
// viewModel.temporaryAvatar.value?.let{ updateProfilePicture(it) }
|
||||||
|
// },
|
||||||
|
// removeAvatar = ::removeProfilePicture
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// updateAvatarDialogImage(viewModel.temporaryAvatar.value)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private fun updateAvatarDialogImage(temporaryAvatar: ByteArray?){
|
||||||
|
// avatarDialog?.update(
|
||||||
|
// temporaryAvatar = temporaryAvatar,
|
||||||
|
// hasUserAvatar = viewModel.hasAvatar()
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
// Helper method used by updateProfilePicture and removeProfilePicture to sync it online
|
// Helper method used by updateProfilePicture and removeProfilePicture to sync it online
|
||||||
private fun syncProfilePicture(profilePicture: ByteArray, onFail: () -> Unit) {
|
private fun syncProfilePicture(profilePicture: ByteArray, onFail: () -> Unit) {
|
||||||
binding.loader.isVisible = true
|
binding.loader.isVisible = true
|
||||||
@ -415,39 +438,16 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
return updateDisplayName(displayName)
|
return updateDisplayName(displayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showEditProfilePictureUI() {
|
|
||||||
showSessionDialog {
|
|
||||||
title(R.string.profileDisplayPictureSet)
|
|
||||||
view(R.layout.dialog_change_avatar)
|
|
||||||
|
|
||||||
// Note: This is the only instance in a dialog where the "Save" button is not a `dangerButton`
|
|
||||||
button(R.string.save) { startAvatarSelection() }
|
|
||||||
|
|
||||||
if (prefs.getProfileAvatarId() != 0) {
|
|
||||||
button(R.string.remove) { removeProfilePicture() }
|
|
||||||
}
|
|
||||||
cancelButton()
|
|
||||||
}.apply {
|
|
||||||
val profilePic = findViewById<ProfilePictureView>(R.id.profile_picture_view)
|
|
||||||
?.also(::setupProfilePictureView)
|
|
||||||
|
|
||||||
val pictureIcon = findViewById<View>(R.id.ic_pictures)
|
|
||||||
|
|
||||||
val recipient = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false)
|
|
||||||
|
|
||||||
val photoSet = (recipient.contactPhoto as ProfileContactPhoto).avatarObject !in setOf("0", "")
|
|
||||||
|
|
||||||
profilePic?.isVisible = photoSet
|
|
||||||
pictureIcon?.isVisible = !photoSet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startAvatarSelection() {
|
private fun startAvatarSelection() {
|
||||||
// Ask for an optional camera permission.
|
// Ask for an optional camera permission.
|
||||||
Permissions.with(this)
|
Permissions.with(this)
|
||||||
.request(Manifest.permission.CAMERA)
|
.request(Manifest.permission.CAMERA)
|
||||||
.onAnyResult {
|
.onAnyResult {
|
||||||
tempFile = avatarSelection.startAvatarSelection( false, true)
|
avatarSelection.startAvatarSelection(
|
||||||
|
includeClear = false,
|
||||||
|
attemptToIncludeCamera = true,
|
||||||
|
createTempFile = viewModel::createTempFile
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.execute()
|
.execute()
|
||||||
}
|
}
|
||||||
@ -574,6 +574,124 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AvatarDialogContainer(
|
||||||
|
startAvatarSelection: ()->Unit,
|
||||||
|
saveAvatar: ()->Unit,
|
||||||
|
removeAvatar: ()->Unit
|
||||||
|
){
|
||||||
|
val state by viewModel.avatarDialogState.collectAsState()
|
||||||
|
|
||||||
|
AvatarDialog(
|
||||||
|
state = state,
|
||||||
|
startAvatarSelection = startAvatarSelection,
|
||||||
|
saveAvatar = saveAvatar,
|
||||||
|
removeAvatar = removeAvatar
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AvatarDialog(
|
||||||
|
state: SettingsViewModel.AvatarDialogState,
|
||||||
|
startAvatarSelection: ()->Unit,
|
||||||
|
saveAvatar: ()->Unit,
|
||||||
|
removeAvatar: ()->Unit
|
||||||
|
){
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {
|
||||||
|
viewModel.onAvatarDialogDismissed()
|
||||||
|
showAvatarDialog = false
|
||||||
|
},
|
||||||
|
title = stringResource(R.string.profileDisplayPictureSet),
|
||||||
|
content = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = LocalDimensions.current.smallSpacing)
|
||||||
|
|
||||||
|
.size(dimensionResource(id = R.dimen.large_profile_picture_size))
|
||||||
|
.clickable {
|
||||||
|
startAvatarSelection()
|
||||||
|
}
|
||||||
|
.background(
|
||||||
|
shape = CircleShape,
|
||||||
|
color = LocalColors.current.backgroundBubbleReceived,
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
when(val s = state){
|
||||||
|
// user avatar
|
||||||
|
is UserAvatar -> {
|
||||||
|
Avatar(userAddress = s.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
// temporary image
|
||||||
|
is TempAvatar -> {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.size(dimensionResource(id = R.dimen.large_profile_picture_size))
|
||||||
|
.clip(shape = CircleShape,),
|
||||||
|
bitmap = BitmapFactory.decodeByteArray(s.data, 0, s.data.size).asImageBitmap(),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty state
|
||||||
|
else -> {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
painter = painterResource(id = R.drawable.ic_pictures),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(LocalColors.current.textSecondary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(LocalDimensions.current.spacing)
|
||||||
|
.background(
|
||||||
|
shape = CircleShape,
|
||||||
|
color = LocalColors.current.primary
|
||||||
|
)
|
||||||
|
.padding(LocalDimensions.current.xxxsSpacing)
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
,
|
||||||
|
painter = painterResource(id = R.drawable.ic_plus),
|
||||||
|
contentDescription = null,
|
||||||
|
colorFilter = ColorFilter.tint(Color.Black)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showCloseButton = true, // display the 'x' button
|
||||||
|
buttons = listOf(
|
||||||
|
DialogButtonModel(
|
||||||
|
text = GetString(R.string.save),
|
||||||
|
contentDescription = GetString(R.string.AccessibilityId_save),
|
||||||
|
onClick = saveAvatar
|
||||||
|
),
|
||||||
|
DialogButtonModel(
|
||||||
|
text = GetString(R.string.remove),
|
||||||
|
contentDescription = GetString(R.string.AccessibilityId_remove),
|
||||||
|
onClick = removeAvatar
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewAvatarDialog(
|
||||||
|
@PreviewParameter(SessionColorsParameterProvider::class) colors: ThemeColors
|
||||||
|
){
|
||||||
|
PreviewTheme(colors) {
|
||||||
|
AvatarDialog(
|
||||||
|
state = NoAvatar,
|
||||||
|
startAvatarSelection = {},
|
||||||
|
saveAvatar = {},
|
||||||
|
removeAvatar = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.hasPaths(): Flow<Boolean> = LocalBroadcastManager.getInstance(this).hasPaths()
|
private fun Context.hasPaths(): Flow<Boolean> = LocalBroadcastManager.getInstance(this).hasPaths()
|
||||||
|
@ -0,0 +1,215 @@
|
|||||||
|
package org.thoughtcrime.securesms.preferences
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.canhub.cropper.CropImage
|
||||||
|
import com.canhub.cropper.CropImageView
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import network.loki.messenger.R
|
||||||
|
import network.loki.messenger.libsession_util.util.UserPic
|
||||||
|
import nl.komponents.kovenant.ui.alwaysUi
|
||||||
|
import nl.komponents.kovenant.ui.failUi
|
||||||
|
import nl.komponents.kovenant.ui.successUi
|
||||||
|
import org.session.libsession.avatars.AvatarHelper
|
||||||
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
|
import org.session.libsession.snode.SnodeAPI
|
||||||
|
import org.session.libsession.utilities.Address
|
||||||
|
import org.session.libsession.utilities.ProfileKeyUtil
|
||||||
|
import org.session.libsession.utilities.ProfilePictureUtilities
|
||||||
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
|
import org.session.libsession.utilities.truncateIdForDisplay
|
||||||
|
import org.session.libsignal.utilities.ExternalStorageUtil.getImageDir
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.session.libsignal.utilities.NoExternalStorageException
|
||||||
|
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||||
|
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
|
||||||
|
import org.thoughtcrime.securesms.util.BitmapDecodingException
|
||||||
|
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||||
|
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||||
|
import org.thoughtcrime.securesms.util.NetworkUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class SettingsViewModel @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
val prefs: TextSecurePreferences
|
||||||
|
) : ViewModel() {
|
||||||
|
private val TAG = "SettingsViewModel"
|
||||||
|
|
||||||
|
private var tempFile: File? = null
|
||||||
|
|
||||||
|
val hexEncodedPublicKey: String get() = prefs.getLocalNumber() ?: ""
|
||||||
|
|
||||||
|
private val _avatarDialogState: MutableStateFlow<AvatarDialogState> = MutableStateFlow(
|
||||||
|
getDefaultAvatarDialogState()
|
||||||
|
)
|
||||||
|
val avatarDialogState: StateFlow<AvatarDialogState>
|
||||||
|
get() = _avatarDialogState
|
||||||
|
|
||||||
|
fun getDisplayName(): String =
|
||||||
|
prefs.getProfileName() ?: truncateIdForDisplay(hexEncodedPublicKey)
|
||||||
|
|
||||||
|
fun hasAvatar() = prefs.getProfileAvatarId() != 0
|
||||||
|
|
||||||
|
fun createTempFile(): File? {
|
||||||
|
try {
|
||||||
|
tempFile = File.createTempFile("avatar-capture", ".jpg", getImageDir(context))
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e("Cannot reserve a temporary avatar capture file.", e)
|
||||||
|
} catch (e: NoExternalStorageException) {
|
||||||
|
Log.e("Cannot reserve a temporary avatar capture file.", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempFile
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTempFile() = tempFile
|
||||||
|
|
||||||
|
fun onAvatarPicked(result: CropImageView.CropResult) {
|
||||||
|
when {
|
||||||
|
result.isSuccessful -> {
|
||||||
|
Log.i(TAG, result.getUriFilePath(context).toString())
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val profilePictureToBeUploaded =
|
||||||
|
BitmapUtil.createScaledBytes(
|
||||||
|
context,
|
||||||
|
result.getUriFilePath(context).toString(),
|
||||||
|
ProfileMediaConstraints()
|
||||||
|
).bitmap
|
||||||
|
|
||||||
|
// update dialog with temporary avatar (has not been saved/uploaded yet)
|
||||||
|
_avatarDialogState.value =
|
||||||
|
AvatarDialogState.TempAvatar(profilePictureToBeUploaded)
|
||||||
|
} catch (e: BitmapDecodingException) {
|
||||||
|
Log.e(TAG, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result is CropImage.CancelledResult -> {
|
||||||
|
Log.i(TAG, "Cropping image was cancelled by the user")
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
Log.e(TAG, "Cropping image failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onAvatarDialogDismissed() {
|
||||||
|
_avatarDialogState.value =getDefaultAvatarDialogState()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDefaultAvatarDialogState() = if (hasAvatar()) AvatarDialogState.UserAvatar(Address.fromSerialized(hexEncodedPublicKey))
|
||||||
|
else AvatarDialogState.NoAvatar
|
||||||
|
|
||||||
|
//todo properly close dialog when done and make sure the state is the right one post change
|
||||||
|
//todo make ripple effect round in dialog avatar picker
|
||||||
|
//todo link other states, like making sure we show the actual avatar if there's already one
|
||||||
|
//todo move upload and remove to VM
|
||||||
|
//todo make buttons in dialog disabled
|
||||||
|
//todo clean up the classes I made which aren't used now...
|
||||||
|
|
||||||
|
sealed class AvatarDialogState() {
|
||||||
|
object NoAvatar : AvatarDialogState()
|
||||||
|
data class UserAvatar(val address: Address) : AvatarDialogState()
|
||||||
|
data class TempAvatar(val data: ByteArray) : AvatarDialogState()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method used by updateProfilePicture and removeProfilePicture to sync it online
|
||||||
|
/*private fun syncProfilePicture(profilePicture: ByteArray, onFail: () -> Unit) {
|
||||||
|
binding.loader.isVisible = true
|
||||||
|
|
||||||
|
// Grab the profile key and kick of the promise to update the profile picture
|
||||||
|
val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this)
|
||||||
|
val updateProfilePicturePromise = ProfilePictureUtilities.upload(profilePicture, encodedProfileKey, this)
|
||||||
|
|
||||||
|
// If the online portion of the update succeeded then update the local state
|
||||||
|
updateProfilePicturePromise.successUi {
|
||||||
|
|
||||||
|
// When removing the profile picture the supplied ByteArray is empty so we'll clear the local data
|
||||||
|
if (profilePicture.isEmpty()) {
|
||||||
|
MessagingModuleConfiguration.shared.storage.clearUserPic()
|
||||||
|
}
|
||||||
|
|
||||||
|
val userConfig = configFactory.user
|
||||||
|
AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture)
|
||||||
|
prefs.setProfileAvatarId(SECURE_RANDOM.nextInt() )
|
||||||
|
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
|
||||||
|
|
||||||
|
// Attempt to grab the details we require to update the profile picture
|
||||||
|
val url = prefs.getProfilePictureURL()
|
||||||
|
val profileKey = ProfileKeyUtil.getProfileKey(this)
|
||||||
|
|
||||||
|
// If we have a URL and a profile key then set the user's profile picture
|
||||||
|
if (!url.isNullOrEmpty() && profileKey.isNotEmpty()) {
|
||||||
|
userConfig?.setPic(UserPic(url, profileKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userConfig != null && userConfig.needsDump()) {
|
||||||
|
configFactory.persist(userConfig, SnodeAPI.nowWithOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@SettingsActivity)
|
||||||
|
|
||||||
|
// Update our visuals
|
||||||
|
binding.profilePictureView.recycle()
|
||||||
|
binding.profilePictureView.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the sync failed then inform the user
|
||||||
|
updateProfilePicturePromise.failUi { onFail() }
|
||||||
|
|
||||||
|
// Finally, remove the loader animation after we've waited for the attempt to succeed or fail
|
||||||
|
updateProfilePicturePromise.alwaysUi { binding.loader.isVisible = false }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateProfilePicture(profilePicture: ByteArray) {
|
||||||
|
|
||||||
|
val haveNetworkConnection = NetworkUtils.haveValidNetworkConnection(this@SettingsActivity);
|
||||||
|
if (!haveNetworkConnection) {
|
||||||
|
Log.w(TAG, "Cannot update profile picture - no network connection.")
|
||||||
|
Toast.makeText(this@SettingsActivity, R.string.profileErrorUpdate, Toast.LENGTH_LONG).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val onFail: () -> Unit = {
|
||||||
|
Log.e(TAG, "Sync failed when uploading profile picture.")
|
||||||
|
Toast.makeText(this@SettingsActivity, R.string.profileErrorUpdate, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
syncProfilePicture(profilePicture, onFail)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeProfilePicture() {
|
||||||
|
|
||||||
|
val haveNetworkConnection = NetworkUtils.haveValidNetworkConnection(this@SettingsActivity);
|
||||||
|
if (!haveNetworkConnection) {
|
||||||
|
Log.w(TAG, "Cannot remove profile picture - no network connection.")
|
||||||
|
Toast.makeText(this@SettingsActivity, R.string.profileDisplayPictureRemoveError, Toast.LENGTH_LONG).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val onFail: () -> Unit = {
|
||||||
|
Log.e(TAG, "Sync failed when removing profile picture.")
|
||||||
|
Toast.makeText(this@SettingsActivity, R.string.profileDisplayPictureRemoveError, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
val emptyProfilePicture = ByteArray(0)
|
||||||
|
syncProfilePicture(emptyProfilePicture, onFail)
|
||||||
|
}*/
|
||||||
|
}
|
@ -65,6 +65,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
|
import org.session.libsession.utilities.Address
|
||||||
import org.session.libsession.utilities.recipients.Recipient
|
import org.session.libsession.utilities.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.components.ProfilePictureView
|
import org.thoughtcrime.securesms.components.ProfilePictureView
|
||||||
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.OptionsCardData
|
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.OptionsCardData
|
||||||
@ -399,22 +400,31 @@ fun Divider(modifier: Modifier = Modifier, startIndent: Dp = 0.dp) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO This component should be fully rebuilt in Compose at some point ~~
|
||||||
@Composable
|
@Composable
|
||||||
fun RowScope.Avatar(recipient: Recipient) {
|
fun Avatar(
|
||||||
Box(
|
recipient: Recipient,
|
||||||
modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
.width(60.dp)
|
) {
|
||||||
.align(Alignment.CenterVertically)
|
AndroidView(
|
||||||
) {
|
factory = {
|
||||||
AndroidView(
|
ProfilePictureView(it).apply { update(recipient) }
|
||||||
factory = {
|
},
|
||||||
ProfilePictureView(it).apply { update(recipient) }
|
modifier = modifier
|
||||||
},
|
)
|
||||||
modifier = Modifier
|
}
|
||||||
.width(46.dp)
|
|
||||||
.height(46.dp)
|
@Composable
|
||||||
)
|
fun Avatar(
|
||||||
}
|
userAddress: Address,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
AndroidView(
|
||||||
|
factory = {
|
||||||
|
ProfilePictureView(it).apply { update(userAddress) }
|
||||||
|
},
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -139,4 +139,9 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<androidx.compose.ui.platform.ComposeView
|
||||||
|
android:id="@+id/avatarDialog"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
@ -1,58 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout
|
|
||||||
android:orientation="vertical"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_width="match_parent">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:id="@+id/ic_pictures"
|
|
||||||
android:background="@drawable/circle_tintable"
|
|
||||||
android:backgroundTint="@color/classic_dark_3"
|
|
||||||
android:layout_marginTop="15dp"
|
|
||||||
android:layout_marginBottom="15dp"
|
|
||||||
android:layout_width="@dimen/large_profile_picture_size"
|
|
||||||
android:layout_height="@dimen/large_profile_picture_size">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@color/transparent"
|
|
||||||
android:src="@drawable/ic_pictures"/>
|
|
||||||
|
|
||||||
<!-- TODO: Add this back when we build the custom modal which allows tapping on the image to select a replacement-->
|
|
||||||
<!-- <LinearLayout-->
|
|
||||||
<!-- android:layout_gravity="bottom|end"-->
|
|
||||||
<!-- android:gravity="center"-->
|
|
||||||
<!-- android:background="@drawable/circle_tintable"-->
|
|
||||||
<!-- android:backgroundTint="?attr/accentColor"-->
|
|
||||||
<!-- android:paddingTop="1dp"-->
|
|
||||||
<!-- android:paddingLeft="1dp"-->
|
|
||||||
<!-- android:layout_width="24dp"-->
|
|
||||||
<!-- android:layout_height="24dp"-->
|
|
||||||
<!-- tools:backgroundTint="@color/accent_green">-->
|
|
||||||
<!-- <View-->
|
|
||||||
<!-- android:background="@drawable/ic_plus"-->
|
|
||||||
<!-- android:backgroundTint="@color/black"-->
|
|
||||||
<!-- android:layout_width="12dp"-->
|
|
||||||
<!-- android:layout_height="12dp"-->
|
|
||||||
<!-- />-->
|
|
||||||
<!-- </LinearLayout>-->
|
|
||||||
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.ProfilePictureView
|
|
||||||
android:layout_margin="30dp"
|
|
||||||
android:id="@+id/profile_picture_view"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:layout_width="@dimen/large_profile_picture_size"
|
|
||||||
android:layout_height="@dimen/large_profile_picture_size"
|
|
||||||
android:layout_marginTop="@dimen/medium_spacing"
|
|
||||||
android:contentDescription="@string/AccessibilityId_profilePicture"
|
|
||||||
tools:visibility="gone"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
@ -43,6 +43,7 @@
|
|||||||
<string name="AccessibilityId_contactUserDetails">Details</string>
|
<string name="AccessibilityId_contactUserDetails">Details</string>
|
||||||
<string name="AccessibilityId_pin">Pin</string>
|
<string name="AccessibilityId_pin">Pin</string>
|
||||||
<string name="AccessibilityId_profilePicture">User settings</string>
|
<string name="AccessibilityId_profilePicture">User settings</string>
|
||||||
|
<string name="AccessibilityId_avatarPicker">Image picker</string>
|
||||||
<string name="AccessibilityId_searchIcon">Search icon</string>
|
<string name="AccessibilityId_searchIcon">Search icon</string>
|
||||||
<!--Settings Page -->
|
<!--Settings Page -->
|
||||||
<string name="AccessibilityId_conversationsBlockedContacts">Blocked contacts</string>
|
<string name="AccessibilityId_conversationsBlockedContacts">Blocked contacts</string>
|
||||||
@ -122,7 +123,7 @@
|
|||||||
<string name="AccessibilityId_message">Message body</string>
|
<string name="AccessibilityId_message">Message body</string>
|
||||||
<string name="AccessibilityId_sent">Message sent status: Sent</string>
|
<string name="AccessibilityId_sent">Message sent status: Sent</string>
|
||||||
<string name="AccessibilityId_reply">Reply to message</string>
|
<string name="AccessibilityId_reply">Reply to message</string>
|
||||||
<string name="AccessibilityId_save">Save attachment</string>
|
<string name="AccessibilityId_saveAttachment">Save attachment</string>
|
||||||
<string name="AccessibilityId_select">Select</string>
|
<string name="AccessibilityId_select">Select</string>
|
||||||
<string name="AccessibilityId_messageVoice">Voice message</string>
|
<string name="AccessibilityId_messageVoice">Voice message</string>
|
||||||
<string name="AccessibilityId_deliveryIndicator">Delivered</string>
|
<string name="AccessibilityId_deliveryIndicator">Delivered</string>
|
||||||
@ -157,5 +158,7 @@
|
|||||||
<string name="AccessibilityId_close">Close Dialog</string>
|
<string name="AccessibilityId_close">Close Dialog</string>
|
||||||
<string name="AccessibilityId_expand">Expand</string>
|
<string name="AccessibilityId_expand">Expand</string>
|
||||||
<string name="AccessibilityId_mediaMessage">Media message</string>
|
<string name="AccessibilityId_mediaMessage">Media message</string>
|
||||||
|
<string name="AccessibilityId_save">Save</string>
|
||||||
|
<string name="AccessibilityId_remove">Remove</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user