mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-25 02:55:23 +00:00
Implement profile picture editing
This commit is contained in:
parent
fd14d66d4f
commit
15b4c6aacc
@ -197,6 +197,7 @@ dependencies {
|
||||
}
|
||||
implementation "com.jakewharton.rxbinding3:rxbinding:3.1.0"
|
||||
implementation "com.github.tbruyelle:rxpermissions:0.10.2"
|
||||
implementation "com.github.ybq:Android-SpinKit:1.4.0"
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 23
|
||||
|
@ -24,7 +24,7 @@
|
||||
android:id="@+id/profileButton"
|
||||
android:layout_width="@dimen/small_profile_picture_size"
|
||||
android:layout_height="@dimen/small_profile_picture_size"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
|
@ -1,15 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/default_session_background">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/default_session_background"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
@ -86,6 +90,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:textAlignment="center"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:visibility="invisible"
|
||||
@ -255,4 +260,24 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/loader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#A4000000"
|
||||
android:visibility="gone"
|
||||
android:alpha="0">
|
||||
|
||||
<com.github.ybq.android.spinkit.SpinKitView
|
||||
style="@style/SpinKitView.Large.ThreeBounce"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_centerInParent="true"
|
||||
app:SpinKit_Color="@color/text" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
@ -8,7 +8,6 @@ import android.support.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper;
|
||||
import org.thoughtcrime.securesms.util.Conversions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@ -17,7 +16,7 @@ import java.security.MessageDigest;
|
||||
public class ProfileContactPhoto implements ContactPhoto {
|
||||
|
||||
private final @NonNull Address address;
|
||||
private final @NonNull String avatarObject;
|
||||
public final @NonNull String avatarObject;
|
||||
|
||||
public ProfileContactPhoto(@NonNull Address address, @NonNull String avatarObject) {
|
||||
this.address = address;
|
||||
|
@ -91,14 +91,14 @@ class RegisterActivity : BaseActionBarActivity() {
|
||||
val hexEncodedPublicKey = keyPair!!.hexEncodedPublicKey
|
||||
val characterCount = hexEncodedPublicKey.count()
|
||||
var count = 0
|
||||
val limit = 40
|
||||
val limit = 32
|
||||
fun animate() {
|
||||
val numberOfIndexesToShuffle = (0 until (40 - count)).random()
|
||||
val numberOfIndexesToShuffle = 32 - count
|
||||
val indexesToShuffle = (0 until characterCount).shuffled().subList(0, numberOfIndexesToShuffle)
|
||||
var mangledHexEncodedPublicKey = hexEncodedPublicKey
|
||||
for (index in indexesToShuffle) {
|
||||
try {
|
||||
mangledHexEncodedPublicKey = mangledHexEncodedPublicKey.substring(0, index) + "0123456789abcdef________________".random() + mangledHexEncodedPublicKey.substring(index + 1, mangledHexEncodedPublicKey.count())
|
||||
mangledHexEncodedPublicKey = mangledHexEncodedPublicKey.substring(0, index) + "0123456789abcdef__".random() + mangledHexEncodedPublicKey.substring(index + 1, mangledHexEncodedPublicKey.count())
|
||||
} catch (exception: Exception) {
|
||||
// Do nothing
|
||||
}
|
||||
@ -108,7 +108,7 @@ class RegisterActivity : BaseActionBarActivity() {
|
||||
publicKeyTextView.text = mangledHexEncodedPublicKey
|
||||
Handler().postDelayed({
|
||||
animate()
|
||||
}, 40)
|
||||
}, 32)
|
||||
} else {
|
||||
publicKeyTextView.text = hexEncodedPublicKey
|
||||
}
|
||||
|
@ -1,30 +1,56 @@
|
||||
package org.thoughtcrime.securesms.loki.redesign.activities
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import kotlinx.android.synthetic.main.activity_settings.*
|
||||
import network.loki.messenger.R
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.all
|
||||
import nl.komponents.kovenant.deferred
|
||||
import nl.komponents.kovenant.ui.alwaysUi
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.avatar.AvatarSelection
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.loki.redesign.utilities.push
|
||||
import org.thoughtcrime.securesms.loki.toPx
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.profiles.AvatarHelper
|
||||
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||
import org.whispersystems.signalservice.api.crypto.ProfileCipher
|
||||
import org.whispersystems.signalservice.api.util.StreamDetails
|
||||
import org.whispersystems.signalservice.loki.api.LokiStorageAPI
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.security.SecureRandom
|
||||
|
||||
class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
private lateinit var glide: GlideRequests
|
||||
private var isEditingDisplayName = false
|
||||
set(value) { field = value; handleIsEditingDisplayNameChanged() }
|
||||
private var displayNameToBeUploaded: String? = null
|
||||
private var profilePictureToBeUploaded: ByteArray? = null
|
||||
private var tempFile: File? = null
|
||||
|
||||
private val hexEncodedPublicKey: String
|
||||
get() {
|
||||
@ -50,6 +76,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
profilePictureView.hexEncodedPublicKey = hexEncodedPublicKey
|
||||
profilePictureView.isLarge = true
|
||||
profilePictureView.update()
|
||||
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
|
||||
// Set up display name container
|
||||
displayNameContainer.setOnClickListener { showEditDisplayNameUI() }
|
||||
// Set up display name text view
|
||||
@ -61,6 +88,34 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
// Set up share button
|
||||
shareButton.setOnClickListener { sharePublicKey() }
|
||||
}
|
||||
|
||||
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
when (requestCode) {
|
||||
AvatarSelection.REQUEST_CODE_AVATAR -> {
|
||||
if (resultCode != Activity.RESULT_OK) { return }
|
||||
val outputFile = Uri.fromFile(File(cacheDir, "cropped"))
|
||||
var inputFile: Uri? = data?.data
|
||||
if (inputFile == null && tempFile != null) {
|
||||
inputFile = Uri.fromFile(tempFile)
|
||||
}
|
||||
AvatarSelection.circularCropImage(this, inputFile, outputFile, R.string.CropImageActivity_profile_avatar)
|
||||
}
|
||||
AvatarSelection.REQUEST_CODE_CROP_IMAGE -> {
|
||||
if (resultCode != Activity.RESULT_OK) { return }
|
||||
AsyncTask.execute {
|
||||
try {
|
||||
profilePictureToBeUploaded = BitmapUtil.createScaledBytes(this@SettingsActivity, AvatarSelection.getResultUri(data), ProfileMediaConstraints()).bitmap
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
updateProfile(true)
|
||||
}
|
||||
} catch (e: BitmapDecodingException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region Updating
|
||||
@ -82,18 +137,62 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateProfile(isUpdatingDisplayName: Boolean, isUpdatingProfilePicture: Boolean) {
|
||||
val displayName = displayNameToBeUploaded ?: TextSecurePreferences.getProfileName(this)
|
||||
TextSecurePreferences.setProfileName(this, displayName)
|
||||
private fun updateProfile(isUpdatingProfilePicture: Boolean) {
|
||||
showLoader()
|
||||
val promises = mutableListOf<Promise<*, Exception>>()
|
||||
val displayName = displayNameToBeUploaded
|
||||
if (displayName != null) {
|
||||
val publicChatAPI = ApplicationContext.getInstance(this).lokiPublicChatAPI
|
||||
if (publicChatAPI != null) {
|
||||
val servers = DatabaseFactory.getLokiThreadDatabase(this).getAllPublicChatServers()
|
||||
for (server in servers) {
|
||||
publicChatAPI.setDisplayName(displayName, server)
|
||||
promises.addAll(servers.map { publicChatAPI.setDisplayName(displayName, it) })
|
||||
}
|
||||
TextSecurePreferences.setProfileName(this, displayName)
|
||||
}
|
||||
val profilePicture = profilePictureToBeUploaded
|
||||
val encodedProfileKey = ProfileKeyUtil.generateEncodedProfileKey(this)
|
||||
val profileKey = ProfileKeyUtil.getProfileKeyFromEncodedString(encodedProfileKey)
|
||||
if (isUpdatingProfilePicture && profilePicture != null) {
|
||||
val storageAPI = LokiStorageAPI.shared
|
||||
val deferred = deferred<Unit, Exception>()
|
||||
AsyncTask.execute {
|
||||
val stream = StreamDetails(ByteArrayInputStream(profilePicture), "image/jpeg", profilePicture.size.toLong())
|
||||
val (_, url) = storageAPI.uploadProfilePicture(storageAPI.server, profileKey, stream)
|
||||
TextSecurePreferences.setProfileAvatarUrl(this, url)
|
||||
deferred.resolve(Unit)
|
||||
}
|
||||
promises.add(deferred.promise)
|
||||
}
|
||||
all(promises).alwaysUi {
|
||||
if (displayName != null) {
|
||||
displayNameTextView.text = displayName
|
||||
}
|
||||
displayNameToBeUploaded = null
|
||||
if (isUpdatingProfilePicture && profilePicture != null) {
|
||||
AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)), profilePicture)
|
||||
TextSecurePreferences.setProfileAvatarId(this, SecureRandom().nextInt())
|
||||
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
|
||||
ApplicationContext.getInstance(this).updatePublicChatProfileAvatarIfNeeded()
|
||||
profilePictureView.update()
|
||||
}
|
||||
profilePictureToBeUploaded = null
|
||||
hideLoader()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLoader() {
|
||||
loader.visibility = View.VISIBLE
|
||||
loader.animate().setDuration(150).alpha(1.0f).start()
|
||||
}
|
||||
|
||||
private fun hideLoader() {
|
||||
loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() {
|
||||
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
super.onAnimationEnd(animation)
|
||||
loader.visibility = View.GONE
|
||||
}
|
||||
})
|
||||
}
|
||||
// endregion
|
||||
|
||||
@ -103,11 +202,19 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
}
|
||||
|
||||
private fun saveDisplayName() {
|
||||
val displayName = displayNameEditText.text.trim().toString()
|
||||
// TODO: Validation
|
||||
val displayName = displayNameEditText.text.toString().trim()
|
||||
if (displayName.isEmpty()) {
|
||||
return Toast.makeText(this, "Please pick a display name", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
if (!displayName.matches(Regex("[a-zA-Z0-9_]+"))) {
|
||||
return Toast.makeText(this, "Please pick a display name that consists of only a-z, A-Z, 0-9 and _ characters", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
if (displayName.toByteArray().size > ProfileCipher.NAME_PADDED_LENGTH) {
|
||||
return Toast.makeText(this, "Please pick a shorter display name", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
isEditingDisplayName = false
|
||||
displayNameToBeUploaded = displayName
|
||||
updateProfile(true, false)
|
||||
updateProfile(false)
|
||||
}
|
||||
|
||||
private fun showQRCode() {
|
||||
@ -115,6 +222,10 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
push(intent)
|
||||
}
|
||||
|
||||
private fun showEditProfilePictureUI() {
|
||||
tempFile = AvatarSelection.startAvatarSelection(this, false, true)
|
||||
}
|
||||
|
||||
private fun showEditDisplayNameUI() {
|
||||
isEditingDisplayName = true
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import android.widget.RelativeLayout
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import kotlinx.android.synthetic.main.view_profile_picture.view.*
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto
|
||||
import org.thoughtcrime.securesms.database.Address
|
||||
import org.thoughtcrime.securesms.loki.JazzIdenticonDrawable
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
@ -60,7 +61,7 @@ class ProfilePictureView : RelativeLayout {
|
||||
glide.clear(imageView)
|
||||
if (hexEncodedPublicKey.isNotEmpty()) {
|
||||
val signalProfilePicture = Recipient.from(context, Address.fromSerialized(hexEncodedPublicKey), false).contactPhoto
|
||||
if (signalProfilePicture != null) {
|
||||
if (signalProfilePicture != null && (signalProfilePicture as? ProfileContactPhoto)?.avatarObject != "0") {
|
||||
glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView)
|
||||
} else {
|
||||
val size = resources.getDimensionPixelSize(sizeID)
|
||||
|
@ -475,7 +475,7 @@ public class Recipient implements RecipientModifiedListener {
|
||||
}
|
||||
|
||||
public synchronized @Nullable ContactPhoto getContactPhoto() {
|
||||
if (isLocalNumber && profileAvatar != null) return new ProfileContactPhoto(address, String.valueOf(TextSecurePreferences.getProfileAvatarId(context)));
|
||||
if (isLocalNumber) return new ProfileContactPhoto(address, String.valueOf(TextSecurePreferences.getProfileAvatarId(context)));
|
||||
else if (isGroupRecipient() && groupAvatarId != null) return new GroupRecordContactPhoto(address, groupAvatarId);
|
||||
else if (systemContactPhoto != null) return new SystemContactPhoto(address, systemContactPhoto, 0);
|
||||
else if (profileAvatar != null) return new ProfileContactPhoto(address, profileAvatar);
|
||||
|
Loading…
Reference in New Issue
Block a user