session-android/src/org/thoughtcrime/securesms/loki/redesign/activities/SettingsActivity.kt

248 lines
11 KiB
Kotlin
Raw Normal View History

2020-01-06 04:26:52 +01:00
package org.thoughtcrime.securesms.loki.redesign.activities
2020-01-07 04:51:11 +01:00
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.app.Activity
2020-01-06 04:26:52 +01:00
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
2020-01-07 04:51:11 +01:00
import android.net.Uri
import android.os.AsyncTask
2020-01-06 04:26:52 +01:00
import android.os.Bundle
2020-01-07 04:51:11 +01:00
import android.os.Handler
import android.os.Looper
2020-01-07 02:00:30 +01:00
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.LinearLayout
2020-01-06 04:26:52 +01:00
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_settings.*
import network.loki.messenger.R
2020-01-07 04:51:11 +01:00
import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.all
import nl.komponents.kovenant.deferred
import nl.komponents.kovenant.ui.alwaysUi
2020-01-07 02:00:30 +01:00
import org.thoughtcrime.securesms.ApplicationContext
2020-01-06 04:26:52 +01:00
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
2020-01-07 04:51:11 +01:00
import org.thoughtcrime.securesms.avatar.AvatarSelection
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.Address
2020-01-06 04:26:52 +01:00
import org.thoughtcrime.securesms.database.DatabaseFactory
2020-01-06 06:05:57 +01:00
import org.thoughtcrime.securesms.loki.redesign.utilities.push
2020-01-07 02:00:30 +01:00
import org.thoughtcrime.securesms.loki.toPx
2020-01-06 04:26:52 +01:00
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests
2020-01-07 04:51:11 +01:00
import org.thoughtcrime.securesms.profiles.AvatarHelper
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
import org.thoughtcrime.securesms.util.BitmapDecodingException
import org.thoughtcrime.securesms.util.BitmapUtil
2020-01-06 04:26:52 +01:00
import org.thoughtcrime.securesms.util.TextSecurePreferences
2020-01-07 04:51:11 +01:00
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
2020-01-06 04:26:52 +01:00
class SettingsActivity : PassphraseRequiredActionBarActivity() {
private lateinit var glide: GlideRequests
2020-01-07 02:00:30 +01:00
private var isEditingDisplayName = false
set(value) { field = value; handleIsEditingDisplayNameChanged() }
private var displayNameToBeUploaded: String? = null
2020-01-07 04:51:11 +01:00
private var profilePictureToBeUploaded: ByteArray? = null
private var tempFile: File? = null
2020-01-06 04:26:52 +01:00
private val hexEncodedPublicKey: String
get() {
val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this)
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this)
return masterHexEncodedPublicKey ?: userHexEncodedPublicKey
}
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
// Set content view
setContentView(R.layout.activity_settings)
2020-01-07 02:00:30 +01:00
// Set custom toolbar
setSupportActionBar(toolbar)
cancelButton.setOnClickListener { cancelEditingDisplayName() }
saveButton.setOnClickListener { saveDisplayName() }
showQRCodeButton.setOnClickListener { showQRCode() }
2020-01-06 04:26:52 +01:00
// Set up Glide
glide = GlideApp.with(this)
// Set up profile picture view
profilePictureView.glide = glide
profilePictureView.hexEncodedPublicKey = hexEncodedPublicKey
profilePictureView.isLarge = true
profilePictureView.update()
2020-01-07 04:51:11 +01:00
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
2020-01-07 02:00:30 +01:00
// Set up display name container
displayNameContainer.setOnClickListener { showEditDisplayNameUI() }
2020-01-06 04:26:52 +01:00
// Set up display name text view
displayNameTextView.text = DatabaseFactory.getLokiUserDatabase(this).getDisplayName(hexEncodedPublicKey)
// Set up public key text view
publicKeyTextView.text = hexEncodedPublicKey
// Set up copy button
copyButton.setOnClickListener { copyPublicKey() }
// Set up share button
shareButton.setOnClickListener { sharePublicKey() }
}
2020-01-07 04:51:11 +01:00
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()
}
}
}
}
}
2020-01-07 02:00:30 +01:00
// endregion
2020-01-06 04:26:52 +01:00
2020-01-07 02:00:30 +01:00
// region Updating
private fun handleIsEditingDisplayNameChanged() {
cancelButton.visibility = if (isEditingDisplayName) View.VISIBLE else View.GONE
showQRCodeButton.visibility = if (isEditingDisplayName) View.GONE else View.VISIBLE
saveButton.visibility = if (isEditingDisplayName) View.VISIBLE else View.GONE
displayNameTextView.visibility = if (isEditingDisplayName) View.INVISIBLE else View.VISIBLE
displayNameEditText.visibility = if (isEditingDisplayName) View.VISIBLE else View.INVISIBLE
val titleTextViewLayoutParams = titleTextView.layoutParams as LinearLayout.LayoutParams
titleTextViewLayoutParams.leftMargin = if (isEditingDisplayName) toPx(16, resources) else 0
titleTextView.layoutParams = titleTextViewLayoutParams
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (isEditingDisplayName) {
displayNameEditText.requestFocus()
inputMethodManager.showSoftInput(displayNameEditText, 0)
} else {
inputMethodManager.hideSoftInputFromWindow(displayNameEditText.windowToken, 0)
}
}
2020-01-07 04:51:11 +01:00
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()
promises.addAll(servers.map { publicChatAPI.setDisplayName(displayName, it) })
2020-01-07 02:00:30 +01:00
}
2020-01-07 04:51:11 +01:00
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()
2020-01-07 02:00:30 +01:00
}
2020-01-07 04:51:11 +01:00
}
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
}
})
2020-01-06 04:26:52 +01:00
}
// endregion
// region Interaction
2020-01-07 02:00:30 +01:00
private fun cancelEditingDisplayName() {
isEditingDisplayName = false
}
private fun saveDisplayName() {
2020-01-07 04:51:11 +01:00
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()
}
2020-01-07 02:00:30 +01:00
isEditingDisplayName = false
displayNameToBeUploaded = displayName
2020-01-07 04:51:11 +01:00
updateProfile(false)
2020-01-06 04:26:52 +01:00
}
private fun showQRCode() {
2020-01-06 06:05:57 +01:00
val intent = Intent(this, QRCodeActivity::class.java)
push(intent)
2020-01-06 04:26:52 +01:00
}
2020-01-07 04:51:11 +01:00
private fun showEditProfilePictureUI() {
tempFile = AvatarSelection.startAvatarSelection(this, false, true)
}
2020-01-07 02:00:30 +01:00
private fun showEditDisplayNameUI() {
isEditingDisplayName = true
}
2020-01-06 04:26:52 +01:00
private fun copyPublicKey() {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Session ID", hexEncodedPublicKey)
clipboard.primaryClip = clip
Toast.makeText(this, R.string.activity_register_public_key_copied_message, Toast.LENGTH_SHORT).show()
}
private fun sharePublicKey() {
val intent = Intent()
intent.action = Intent.ACTION_SEND
intent.putExtra(Intent.EXTRA_TEXT, hexEncodedPublicKey)
intent.type = "text/plain"
startActivity(intent)
}
// endregion
}