session-android/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt

318 lines
13 KiB
Kotlin
Raw Normal View History

2021-07-09 03:14:21 +02:00
package org.thoughtcrime.securesms.groups
2020-06-09 07:03:43 +02:00
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.inputmethod.EditorInfo
2020-06-09 07:03:43 +02:00
import android.view.inputmethod.InputMethodManager
import android.widget.*
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
2021-01-21 05:42:43 +01:00
import kotlinx.android.synthetic.main.activity_settings.*
import network.loki.messenger.R
2021-01-13 06:13:49 +01:00
import nl.komponents.kovenant.Promise
2021-02-02 07:05:21 +01:00
import nl.komponents.kovenant.task
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
2021-03-04 04:54:32 +01:00
import org.session.libsession.messaging.sending_receiving.MessageSender
2021-03-12 04:52:59 +01:00
import org.session.libsession.messaging.sending_receiving.groupSizeLimit
2021-05-18 08:11:38 +02:00
import org.session.libsession.utilities.Address
2021-02-17 06:09:36 +01:00
import org.session.libsession.utilities.GroupUtil
2021-01-15 05:36:30 +01:00
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.ThemeUtil
import org.session.libsession.utilities.recipients.Recipient
2021-05-18 01:34:45 +02:00
import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
2021-07-09 03:14:21 +02:00
import org.thoughtcrime.securesms.contacts.SelectContactsActivity
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.util.fadeIn
import org.thoughtcrime.securesms.util.fadeOut
import java.io.IOException
2020-08-14 06:52:15 +02:00
class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
private val originalMembers = HashSet<String>()
2021-04-21 08:34:22 +02:00
private val zombies = HashSet<String>()
private val members = HashSet<String>()
private val allMembers: Set<String>
get() {
2021-04-28 08:00:13 +02:00
return members + zombies
}
2020-08-18 01:34:22 +02:00
private var hasNameChanged = false
2021-04-21 08:34:22 +02:00
private var isSelfAdmin = false
private var isLoading = false
set(newValue) { field = newValue; invalidateOptionsMenu() }
2020-08-14 06:52:15 +02:00
private lateinit var groupID: String
2020-08-14 06:52:15 +02:00
private lateinit var originalName: String
2020-08-18 01:34:22 +02:00
private lateinit var name: String
2020-08-18 01:34:22 +02:00
private var isEditingName = false
set(value) {
if (field == value) return
field = value
2020-08-18 01:34:22 +02:00
handleIsEditingNameChanged()
}
2020-08-18 01:34:22 +02:00
private val memberListAdapter by lazy {
2021-04-21 08:34:22 +02:00
if (isSelfAdmin)
EditClosedGroupMembersAdapter(this, GlideApp.with(this), isSelfAdmin, this::onMemberClick)
else
EditClosedGroupMembersAdapter(this, GlideApp.with(this), isSelfAdmin)
2020-08-18 01:34:22 +02:00
}
private lateinit var mainContentContainer: LinearLayout
private lateinit var cntGroupNameEdit: LinearLayout
private lateinit var cntGroupNameDisplay: LinearLayout
private lateinit var edtGroupName: EditText
private lateinit var emptyStateContainer: LinearLayout
private lateinit var lblGroupNameDisplay: TextView
private lateinit var loaderContainer: View
2020-08-18 01:34:22 +02:00
companion object {
@JvmStatic val groupIDKey = "groupIDKey"
private val loaderID = 0
val addUsersRequestCode = 124
val legacyGroupSizeLimit = 10
}
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
2020-08-18 01:34:22 +02:00
setContentView(R.layout.activity_edit_closed_group)
2020-08-14 06:52:15 +02:00
supportActionBar!!.setHomeAsUpIndicator(
ThemeUtil.getThemedDrawableResId(this, R.attr.actionModeCloseDrawable))
groupID = intent.getStringExtra(groupIDKey)!!
val groupInfo = DatabaseComponent.get(this).groupDatabase().getGroup(groupID).get()
2021-04-21 08:34:22 +02:00
originalName = groupInfo.title
isSelfAdmin = groupInfo.admins.any{ it.serialize() == TextSecurePreferences.getLocalNumber(this) }
2020-08-18 01:34:22 +02:00
name = originalName
mainContentContainer = findViewById(R.id.mainContentContainer)
cntGroupNameEdit = findViewById(R.id.cntGroupNameEdit)
cntGroupNameDisplay = findViewById(R.id.cntGroupNameDisplay)
edtGroupName = findViewById(R.id.edtGroupName)
emptyStateContainer = findViewById(R.id.emptyStateContainer)
lblGroupNameDisplay = findViewById(R.id.lblGroupNameDisplay)
loaderContainer = findViewById(R.id.loaderContainer)
2020-08-14 06:52:15 +02:00
findViewById<View>(R.id.addMembersClosedGroupButton).setOnClickListener {
onAddMembersClick()
}
findViewById<RecyclerView>(R.id.rvUserList).apply {
adapter = memberListAdapter
layoutManager = LinearLayoutManager(this@EditClosedGroupActivity)
}
2020-08-14 06:52:15 +02:00
2020-08-18 01:34:22 +02:00
lblGroupNameDisplay.text = originalName
cntGroupNameDisplay.setOnClickListener { isEditingName = true }
findViewById<View>(R.id.btnCancelGroupNameEdit).setOnClickListener { isEditingName = false }
findViewById<View>(R.id.btnSaveGroupNameEdit).setOnClickListener { saveName() }
edtGroupName.setImeActionLabel(getString(R.string.save), EditorInfo.IME_ACTION_DONE)
edtGroupName.setOnEditorActionListener { _, actionId, _ ->
when (actionId) {
EditorInfo.IME_ACTION_DONE -> {
2020-08-18 01:34:22 +02:00
saveName()
return@setOnEditorActionListener true
}
else -> return@setOnEditorActionListener false
}
}
LoaderManager.getInstance(this).initLoader(loaderID, null, object : LoaderManager.LoaderCallbacks<GroupMembers> {
2020-08-18 01:34:22 +02:00
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<GroupMembers> {
return EditClosedGroupLoader(this@EditClosedGroupActivity, groupID)
2020-08-14 06:52:15 +02:00
}
override fun onLoadFinished(loader: Loader<GroupMembers>, groupMembers: GroupMembers) {
2020-08-14 06:52:15 +02:00
// We no longer need any subsequent loading events
// (they will occur on every activity resume).
LoaderManager.getInstance(this@EditClosedGroupActivity).destroyLoader(loaderID)
2020-08-14 06:52:15 +02:00
members.clear()
members.addAll(groupMembers.members.toHashSet())
zombies.clear()
zombies.addAll(groupMembers.zombieMembers.toHashSet())
2020-08-14 06:52:15 +02:00
originalMembers.clear()
originalMembers.addAll(members + zombies)
updateMembers()
2020-08-14 06:52:15 +02:00
}
override fun onLoaderReset(loader: Loader<GroupMembers>) {
updateMembers()
2020-08-14 06:52:15 +02:00
}
})
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_edit_closed_group, menu)
return allMembers.isNotEmpty() && !isLoading
}
// endregion
// region Updating
2020-08-18 01:34:22 +02:00
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
addUsersRequestCode -> {
2020-08-18 01:34:22 +02:00
if (resultCode != RESULT_OK) return
if (data == null || data.extras == null || !data.hasExtra(SelectContactsActivity.selectedContactsKey)) return
val selectedContacts = data.extras!!.getStringArray(SelectContactsActivity.selectedContactsKey)!!.toSet()
members.addAll(selectedContacts)
updateMembers()
2020-08-18 01:34:22 +02:00
}
}
}
private fun handleIsEditingNameChanged() {
cntGroupNameEdit.visibility = if (isEditingName) View.VISIBLE else View.INVISIBLE
cntGroupNameDisplay.visibility = if (isEditingName) View.INVISIBLE else View.VISIBLE
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (isEditingName) {
edtGroupName.setText(name)
edtGroupName.selectAll()
edtGroupName.requestFocus()
inputMethodManager.showSoftInput(edtGroupName, 0)
} else {
inputMethodManager.hideSoftInputFromWindow(edtGroupName.windowToken, 0)
}
}
private fun updateMembers() {
memberListAdapter.setMembers(allMembers)
memberListAdapter.setZombieMembers(zombies)
mainContentContainer.visibility = if (allMembers.isEmpty()) View.GONE else View.VISIBLE
emptyStateContainer.visibility = if (allMembers.isEmpty()) View.VISIBLE else View.GONE
2020-08-18 01:34:22 +02:00
invalidateOptionsMenu()
}
// endregion
// region Interaction
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_apply -> if (!isLoading) { commitChanges() }
}
return super.onOptionsItemSelected(item)
}
2020-08-14 06:52:15 +02:00
private fun onMemberClick(member: String) {
2020-08-18 01:34:22 +02:00
val bottomSheet = ClosedGroupEditingOptionsBottomSheet()
bottomSheet.onRemoveTapped = {
if (zombies.contains(member)) zombies.remove(member)
else members.remove(member)
updateMembers()
bottomSheet.dismiss()
}
2020-08-18 01:34:22 +02:00
bottomSheet.show(supportFragmentManager, "GroupEditingOptionsBottomSheet")
}
2020-08-14 06:52:15 +02:00
private fun onAddMembersClick() {
val intent = Intent(this@EditClosedGroupActivity, SelectContactsActivity::class.java)
intent.putExtra(SelectContactsActivity.usersToExcludeKey, allMembers.toTypedArray())
2020-10-02 07:24:13 +02:00
intent.putExtra(SelectContactsActivity.emptyStateTextKey, "No contacts to add")
startActivityForResult(intent, addUsersRequestCode)
}
2020-08-18 01:34:22 +02:00
private fun saveName() {
val name = edtGroupName.text.toString().trim()
if (name.isEmpty()) {
return Toast.makeText(this, R.string.activity_edit_closed_group_group_name_missing_error, Toast.LENGTH_SHORT).show()
}
2020-08-18 01:34:22 +02:00
if (name.length >= 64) {
return Toast.makeText(this, R.string.activity_edit_closed_group_group_name_too_long_error, Toast.LENGTH_SHORT).show()
}
2020-08-18 01:34:22 +02:00
this.name = name
lblGroupNameDisplay.text = name
hasNameChanged = true
isEditingName = false
}
2020-08-18 01:34:22 +02:00
private fun commitChanges() {
val hasMemberListChanges = (allMembers != originalMembers)
2020-08-14 06:52:15 +02:00
2020-08-18 01:34:22 +02:00
if (!hasNameChanged && !hasMemberListChanges) {
return finish()
}
2020-08-14 06:52:15 +02:00
2020-08-18 01:34:22 +02:00
val name = if (hasNameChanged) this.name else originalName
2020-08-14 06:52:15 +02:00
val members = this.allMembers.map {
2020-08-14 06:52:15 +02:00
Recipient.from(this, Address.fromSerialized(it), false)
}.toSet()
2021-02-02 07:05:21 +01:00
val originalMembers = this.originalMembers.map {
Recipient.from(this, Address.fromSerialized(it), false)
}.toSet()
2021-02-18 04:14:05 +01:00
var isClosedGroup: Boolean
var groupPublicKey: String?
try {
2021-02-17 06:09:36 +01:00
groupPublicKey = GroupUtil.doubleDecodeGroupID(groupID).toHexString()
isClosedGroup = DatabaseComponent.get(this).lokiAPIDatabase().isClosedGroup(groupPublicKey)
} catch (e: IOException) {
groupPublicKey = null
2021-02-18 04:14:05 +01:00
isClosedGroup = false
}
2021-01-27 05:54:12 +01:00
if (members.isEmpty()) {
return Toast.makeText(this, R.string.activity_edit_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
}
2020-08-18 01:34:22 +02:00
2021-03-12 04:52:59 +01:00
val maxGroupMembers = if (isClosedGroup) groupSizeLimit else legacyGroupSizeLimit
if (members.size >= maxGroupMembers) {
2021-01-27 05:54:12 +01:00
return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
}
2021-01-15 05:36:30 +01:00
val userPublicKey = TextSecurePreferences.getLocalNumber(this)!!
2021-01-13 06:13:49 +01:00
val userAsRecipient = Recipient.from(this, Address.fromSerialized(userPublicKey), false)
2021-01-15 05:36:30 +01:00
if (!members.contains(userAsRecipient) && !members.map { it.address.toString() }.containsAll(originalMembers.minus(userPublicKey))) {
2021-01-13 06:13:49 +01:00
val message = "Can't leave while adding or removing other members."
return Toast.makeText(this@EditClosedGroupActivity, message, Toast.LENGTH_LONG).show()
}
2021-02-18 04:14:05 +01:00
if (isClosedGroup) {
isLoading = true
loaderContainer.fadeIn()
2021-02-02 07:05:21 +01:00
val promise: Promise<Any, Exception> = if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) {
2021-03-26 05:46:37 +01:00
MessageSender.explicitLeave(groupPublicKey!!, true)
2021-01-13 06:13:49 +01:00
} else {
2021-02-11 06:34:01 +01:00
task {
if (hasNameChanged) {
2021-03-04 04:54:32 +01:00
MessageSender.explicitNameChange(groupPublicKey!!, name)
}
2021-02-11 06:34:01 +01:00
members.filterNot { it in originalMembers }.let { adds ->
2021-03-04 04:54:32 +01:00
if (adds.isNotEmpty()) MessageSender.explicitAddMembers(groupPublicKey!!, adds.map { it.address.serialize() })
}
2021-02-11 06:34:01 +01:00
originalMembers.filterNot { it in members }.let { removes ->
2021-03-04 04:54:32 +01:00
if (removes.isNotEmpty()) MessageSender.explicitRemoveMembers(groupPublicKey!!, removes.map { it.address.serialize() })
}
2021-02-11 06:34:01 +01:00
}
2021-01-13 06:13:49 +01:00
}
promise.successUi {
loaderContainer.fadeOut()
isLoading = false
finish()
}.failUi { exception ->
2021-03-04 04:54:32 +01:00
val message = if (exception is MessageSender.Error) exception.description else "An error occurred"
Toast.makeText(this@EditClosedGroupActivity, message, Toast.LENGTH_LONG).show()
loaderContainer.fadeOut()
isLoading = false
}
}
}
class GroupMembers(val members: List<String>, val zombieMembers: List<String>) { }
}