2021-07-09 03:14:21 +02:00
package org.thoughtcrime.securesms.preferences
2020-01-06 04:26:52 +01:00
2020-09-07 13:03:15 +02:00
import android.Manifest
2020-01-07 04:51:11 +01:00
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
2020-08-27 06:38:41 +02:00
import android.os.AsyncTask
import android.os.Bundle
import android.os.Handler
import android.os.Looper
2020-08-20 09:48:41 +02:00
import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
2020-01-07 02:00:30 +01:00
import android.view.View
import android.view.inputmethod.InputMethodManager
2020-01-06 04:26:52 +01:00
import android.widget.Toast
2021-02-24 05:28:48 +01:00
import androidx.core.view.isVisible
2020-01-06 04:26:52 +01:00
import kotlinx.android.synthetic.main.activity_settings.*
2020-02-25 04:04:15 +01:00
import network.loki.messenger.BuildConfig
2020-01-06 04:26:52 +01:00
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.ui.alwaysUi
2021-05-13 07:27:08 +02:00
import nl.komponents.kovenant.ui.successUi
2021-05-18 08:03:47 +02:00
import org.session.libsession.avatars.AvatarHelper
2021-05-18 08:11:38 +02:00
import org.session.libsession.utilities.Address
2021-07-07 06:22:04 +02:00
import org.session.libsession.utilities.ProfileKeyUtil
2021-05-13 06:24:27 +02:00
import org.session.libsession.utilities.ProfilePictureUtilities
2021-03-03 01:33:35 +01:00
import org.session.libsession.utilities.SSKEnvironment.ProfileManagerProtocol
import org.session.libsession.utilities.TextSecurePreferences
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
2020-01-06 04:26:52 +01:00
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests
2020-09-07 13:03:15 +02:00
import org.thoughtcrime.securesms.permissions.Permissions
2020-01-07 04:51:11 +01:00
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints
2021-09-17 08:44:32 +02:00
import org.thoughtcrime.securesms.util.*
2020-01-07 04:51:11 +01:00
import java.io.File
import java.security.SecureRandom
2020-02-24 04:57:51 +01:00
import java.util.*
2020-01-06 04:26:52 +01:00
class SettingsActivity : PassphraseRequiredActionBarActivity ( ) {
2020-08-20 09:48:41 +02:00
private var displayNameEditActionMode : ActionMode ? = null
set ( value ) { field = value ; handleDisplayNameEditActionModeChanged ( ) }
2020-01-06 04:26:52 +01:00
private lateinit var glide : GlideRequests
2020-01-07 02:00:30 +01:00
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 ( ) {
2021-02-18 05:12:30 +01:00
return TextSecurePreferences . getLocalNumber ( this ) !!
2020-01-06 04:26:52 +01:00
}
2021-03-01 07:16:15 +01:00
companion object {
const val updatedProfileResultCode = 1234
}
2020-01-06 04:26:52 +01:00
// region Lifecycle
override fun onCreate ( savedInstanceState : Bundle ? , isReady : Boolean ) {
super . onCreate ( savedInstanceState , isReady )
setContentView ( R . layout . activity _settings )
2021-05-24 02:27:31 +02:00
val displayName = TextSecurePreferences . getProfileName ( this ) ?: hexEncodedPublicKey
2020-01-06 04:26:52 +01:00
glide = GlideApp . with ( this )
profilePictureView . glide = glide
2020-07-15 06:26:20 +02:00
profilePictureView . publicKey = hexEncodedPublicKey
2020-09-09 05:57:22 +02:00
profilePictureView . displayName = displayName
2020-01-06 04:26:52 +01:00
profilePictureView . isLarge = true
profilePictureView . update ( )
2020-01-07 04:51:11 +01:00
profilePictureView . setOnClickListener { showEditProfilePictureUI ( ) }
2020-08-20 09:48:41 +02:00
ctnGroupNameSection . setOnClickListener { startActionMode ( DisplayNameEditActionModeCallback ( ) ) }
2020-09-09 05:57:22 +02:00
btnGroupNameDisplay . text = displayName
2020-01-06 04:26:52 +01:00
publicKeyTextView . text = hexEncodedPublicKey
copyButton . setOnClickListener { copyPublicKey ( ) }
shareButton . setOnClickListener { sharePublicKey ( ) }
2020-01-09 01:35:43 +01:00
privacyButton . setOnClickListener { showPrivacySettings ( ) }
2020-01-09 04:15:43 +01:00
notificationsButton . setOnClickListener { showNotificationSettings ( ) }
chatsButton . setOnClickListener { showChatSettings ( ) }
2020-11-02 04:54:10 +01:00
sendInvitationButton . setOnClickListener { sendInvitation ( ) }
2021-07-14 06:17:40 +02:00
faqButton . setOnClickListener { showFAQ ( ) }
2021-09-17 08:44:32 +02:00
surveyButton . setOnClickListener { showSurvey ( ) }
2021-05-19 05:47:04 +02:00
helpTranslateButton . setOnClickListener { helpTranslate ( ) }
2020-01-07 05:59:18 +01:00
seedButton . setOnClickListener { showSeed ( ) }
2020-01-07 06:11:02 +01:00
clearAllDataButton . setOnClickListener { clearAllData ( ) }
2021-09-23 05:49:32 +02:00
supportButton . setOnClickListener { shareLogs ( ) }
2021-07-08 02:42:42 +02:00
val isLightMode = UiModeUtilities . isDayUiMode ( this )
oxenLogoImageView . setImageResource ( if ( isLightMode ) R . drawable . oxen _light _mode else R . drawable . oxen _dark _mode )
2020-09-01 08:27:10 +02:00
versionTextView . text = String . format ( getString ( R . string . version _s ) , " ${BuildConfig.VERSION_NAME} ( ${BuildConfig.VERSION_CODE} ) " )
2020-08-19 03:48:16 +02:00
}
2020-08-20 09:48:41 +02:00
override fun onCreateOptionsMenu ( menu : Menu ) : Boolean {
menuInflater . inflate ( R . menu . settings _general , menu )
2020-09-09 05:57:22 +02:00
// Update UI mode menu icon
2020-09-02 13:09:38 +02:00
val uiMode = UiModeUtilities . getUserSelectedUiMode ( this )
menu . findItem ( R . id . action _change _theme ) . icon !! . level = uiMode . ordinal
2020-08-20 09:48:41 +02:00
return true
2020-08-19 03:48:16 +02:00
}
2020-08-20 09:48:41 +02:00
override fun onOptionsItemSelected ( item : MenuItem ) : Boolean {
return when ( item . itemId ) {
R . id . action _qr _code -> {
showQRCode ( )
2020-08-26 15:12:01 +02:00
true
}
R . id . action _change _theme -> {
2020-08-27 06:38:25 +02:00
ChangeUiModeDialog ( ) . show ( supportFragmentManager , ChangeUiModeDialog . TAG )
2020-08-26 15:12:01 +02:00
true
2020-08-20 09:48:41 +02:00
}
else -> super . onOptionsItemSelected ( item )
}
2020-01-06 04:26:52 +01:00
}
2020-01-07 04:51:11 +01:00
2020-08-20 09:48:41 +02:00
override fun onActivityResult ( requestCode : Int , resultCode : Int , data : Intent ? ) {
2020-01-07 04:51:11 +01:00
super . onActivityResult ( requestCode , resultCode , data )
when ( requestCode ) {
AvatarSelection . REQUEST _CODE _AVATAR -> {
2021-05-13 06:24:27 +02:00
if ( resultCode != Activity . RESULT _OK ) {
return
}
2020-01-07 04:51:11 +01:00
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 -> {
2021-05-13 06:24:27 +02:00
if ( resultCode != Activity . RESULT _OK ) {
return
}
2020-01-07 04:51:11 +01:00
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-09-07 13:03:15 +02:00
override fun onRequestPermissionsResult ( requestCode : Int , permissions : Array < out String > , grantResults : IntArray ) {
super . onRequestPermissionsResult ( requestCode , permissions , grantResults )
Permissions . onRequestPermissionsResult ( this , requestCode , permissions , grantResults )
}
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
2020-08-20 09:48:41 +02:00
private fun handleDisplayNameEditActionModeChanged ( ) {
val isEditingDisplayName = this . displayNameEditActionMode !== null
2020-08-14 11:26:56 +02:00
btnGroupNameDisplay . visibility = if ( isEditingDisplayName ) View . INVISIBLE else View . VISIBLE
2020-01-07 02:00:30 +01:00
displayNameEditText . visibility = if ( isEditingDisplayName ) View . VISIBLE else View . INVISIBLE
2020-08-20 09:48:41 +02:00
2020-01-07 02:00:30 +01:00
val inputMethodManager = getSystemService ( Context . INPUT _METHOD _SERVICE ) as InputMethodManager
if ( isEditingDisplayName ) {
2020-08-20 09:48:41 +02:00
displayNameEditText . setText ( btnGroupNameDisplay . text )
displayNameEditText . selectAll ( )
2020-01-07 02:00:30 +01:00
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 ) {
2021-02-24 05:28:48 +01:00
loader . isVisible = true
2020-01-07 04:51:11 +01:00
val promises = mutableListOf < Promise < * , Exception > > ( )
val displayName = displayNameToBeUploaded
if ( displayName != null ) {
TextSecurePreferences . setProfileName ( this , displayName )
}
val profilePicture = profilePictureToBeUploaded
val encodedProfileKey = ProfileKeyUtil . generateEncodedProfileKey ( this )
if ( isUpdatingProfilePicture && profilePicture != null ) {
2021-05-13 06:24:27 +02:00
promises . add ( ProfilePictureUtilities . upload ( profilePicture , encodedProfileKey , this ) )
2020-01-07 04:51:11 +01:00
}
2021-05-13 06:24:27 +02:00
val compoundPromise = all ( promises )
2021-05-13 07:27:08 +02:00
compoundPromise . successUi { // Do this on the UI thread so that it happens before the alwaysUi clause below
2021-05-13 06:24:27 +02:00
if ( isUpdatingProfilePicture && profilePicture != null ) {
AvatarHelper . setAvatar ( this , Address . fromSerialized ( TextSecurePreferences . getLocalNumber ( this ) !! ) , profilePicture )
TextSecurePreferences . setProfileAvatarId ( this , SecureRandom ( ) . nextInt ( ) )
TextSecurePreferences . setLastProfilePictureUpload ( this , Date ( ) . time )
ProfileKeyUtil . setEncodedProfileKey ( this , encodedProfileKey )
}
2021-02-24 05:28:48 +01:00
if ( profilePicture != null || displayName != null ) {
2021-07-09 05:01:16 +02:00
ConfigurationMessageUtilities . forceSyncConfigurationNowIfNeeded ( this @SettingsActivity )
2021-02-24 05:28:48 +01:00
}
2021-05-13 06:24:27 +02:00
}
compoundPromise . alwaysUi {
2020-01-07 04:51:11 +01:00
if ( displayName != null ) {
2020-08-14 11:26:56 +02:00
btnGroupNameDisplay . text = displayName
2020-01-07 04:51:11 +01:00
}
if ( isUpdatingProfilePicture && profilePicture != null ) {
2021-05-13 07:27:08 +02:00
profilePictureView . recycle ( ) // Clear the cached image before updating
2020-01-07 04:51:11 +01:00
profilePictureView . update ( )
}
2021-03-03 01:33:35 +01:00
displayNameToBeUploaded = null
2020-01-07 04:51:11 +01:00
profilePictureToBeUploaded = null
2021-02-24 05:28:48 +01:00
loader . isVisible = false
2020-01-07 02:00:30 +01:00
}
2020-01-07 04:51:11 +01:00
}
2020-01-06 04:26:52 +01:00
// endregion
// region Interaction
2020-01-07 02:00:30 +01:00
2020-08-20 09:48:41 +02:00
/ * *
* @return true if the update was successful .
* /
private fun saveDisplayName ( ) : Boolean {
2020-01-07 04:51:11 +01:00
val displayName = displayNameEditText . text . toString ( ) . trim ( )
if ( displayName . isEmpty ( ) ) {
2020-08-20 09:48:41 +02:00
Toast . makeText ( this , R . string . activity _settings _display _name _missing _error , Toast . LENGTH _SHORT ) . show ( )
return false
2020-01-07 04:51:11 +01:00
}
2021-02-22 01:29:22 +01:00
if ( displayName . toByteArray ( ) . size > ProfileManagerProtocol . Companion . NAME _PADDED _LENGTH ) {
2020-08-20 09:48:41 +02:00
Toast . makeText ( this , R . string . activity _settings _display _name _too _long _error , Toast . LENGTH _SHORT ) . show ( )
return false
2020-01-07 04:51:11 +01:00
}
2020-01-07 02:00:30 +01:00
displayNameToBeUploaded = displayName
2020-01-07 04:51:11 +01:00
updateProfile ( false )
2020-08-20 09:48:41 +02:00
return true
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 ( ) {
2020-09-07 13:03:15 +02:00
// Ask for an optional camera permission.
Permissions . with ( this )
2021-07-14 06:17:40 +02:00
. request ( Manifest . permission . CAMERA )
. onAnyResult {
tempFile = AvatarSelection . startAvatarSelection ( this , false , true )
}
. execute ( )
2020-01-07 04:51:11 +01:00
}
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 )
2020-08-19 02:06:26 +02:00
clipboard . setPrimaryClip ( clip )
2020-05-25 08:38:36 +02:00
Toast . makeText ( this , R . string . copied _to _clipboard , Toast . LENGTH _SHORT ) . show ( )
2020-01-06 04:26:52 +01:00
}
private fun sharePublicKey ( ) {
val intent = Intent ( )
intent . action = Intent . ACTION _SEND
intent . putExtra ( Intent . EXTRA _TEXT , hexEncodedPublicKey )
intent . type = " text/plain "
2021-07-07 06:22:04 +02:00
val chooser = Intent . createChooser ( intent , getString ( R . string . share ) )
startActivity ( chooser )
2020-01-06 04:26:52 +01:00
}
2020-01-07 05:59:18 +01:00
2020-01-09 01:35:43 +01:00
private fun showPrivacySettings ( ) {
2020-01-09 04:15:43 +01:00
val intent = Intent ( this , PrivacySettingsActivity :: class . java )
push ( intent )
}
private fun showNotificationSettings ( ) {
val intent = Intent ( this , NotificationSettingsActivity :: class . java )
push ( intent )
}
private fun showChatSettings ( ) {
val intent = Intent ( this , ChatSettingsActivity :: class . java )
2020-01-09 01:35:43 +01:00
push ( intent )
}
2020-11-02 04:54:10 +01:00
private fun sendInvitation ( ) {
val intent = Intent ( )
intent . action = Intent . ACTION _SEND
2021-07-14 07:08:59 +02:00
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 ! "
2020-11-02 04:54:10 +01:00
intent . putExtra ( Intent . EXTRA _TEXT , invitation )
intent . type = " text/plain "
2021-07-07 07:51:20 +02:00
val chooser = Intent . createChooser ( intent , getString ( R . string . activity _settings _invite _button _title ) )
2021-07-07 06:22:04 +02:00
startActivity ( chooser )
2020-11-02 04:54:10 +01:00
}
2021-07-14 06:17:40 +02:00
private fun showFAQ ( ) {
try {
val url = " https://getsession.org/faq "
val intent = Intent ( Intent . ACTION _VIEW , Uri . parse ( url ) )
startActivity ( intent )
} catch ( e : Exception ) {
Toast . makeText ( this , " Can't open URL " , Toast . LENGTH _LONG ) . show ( )
}
}
2021-09-17 08:44:32 +02:00
private fun showSurvey ( ) {
try {
val url = " https://getsession.org/survey "
val intent = Intent ( Intent . ACTION _VIEW , Uri . parse ( url ) )
startActivity ( intent )
} catch ( e : Exception ) {
Toast . makeText ( this , " Can't open URL " , Toast . LENGTH _LONG ) . show ( )
}
}
2021-05-19 05:47:04 +02:00
private fun helpTranslate ( ) {
try {
val url = " https://crowdin.com/project/session-android "
val intent = Intent ( Intent . ACTION _VIEW , Uri . parse ( url ) )
startActivity ( intent )
} catch ( e : Exception ) {
Toast . makeText ( this , " Can't open URL " , Toast . LENGTH _LONG ) . show ( )
}
}
2020-01-07 05:59:18 +01:00
private fun showSeed ( ) {
2020-01-08 05:16:34 +01:00
SeedDialog ( ) . show ( supportFragmentManager , " Recovery Phrase Dialog " )
2020-01-07 05:59:18 +01:00
}
2020-01-07 06:11:02 +01:00
private fun clearAllData ( ) {
2021-06-22 09:01:27 +02:00
ClearAllDataDialog ( ) . show ( supportFragmentManager , " Clear All Data Dialog " )
2021-06-18 08:01:34 +02:00
}
2021-09-23 05:49:32 +02:00
private fun shareLogs ( ) {
ShareLogsDialog ( ) . show ( supportFragmentManager , " Share Logs Dialog " )
}
2020-01-06 04:26:52 +01:00
// endregion
2020-08-20 09:48:41 +02:00
private inner class DisplayNameEditActionModeCallback : ActionMode . Callback {
override fun onCreateActionMode ( mode : ActionMode , menu : Menu ) : Boolean {
mode . title = getString ( R . string . activity _settings _display _name _edit _text _hint )
mode . menuInflater . inflate ( R . menu . menu _apply , menu )
this @SettingsActivity . displayNameEditActionMode = mode
return true
}
override fun onPrepareActionMode ( mode : ActionMode , menu : Menu ) : Boolean {
return false
}
override fun onDestroyActionMode ( mode : ActionMode ) {
this @SettingsActivity . displayNameEditActionMode = null
}
override fun onActionItemClicked ( mode : ActionMode , item : MenuItem ) : Boolean {
when ( item . itemId ) {
R . id . applyButton -> {
if ( this @SettingsActivity . saveDisplayName ( ) ) {
mode . finish ( )
}
return true
}
}
return false ;
}
}
2020-01-06 04:26:52 +01:00
}