diff --git a/app/build.gradle b/app/build.gradle
index 15546840f..393994bb3 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -18,6 +18,7 @@ plugins {
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
id 'com.google.devtools.ksp' version "$kspVersion"
+ id 'app.cash.molecule'
}
apply plugin: 'com.android.application'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 608069604..9668b0d83 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -152,6 +152,10 @@
android:theme="@style/Theme.Session.DayNight.FlatActionBar"
android:label="@string/blocked_contacts_title"
/>
+
{
val recipient = viewModel.recipient ?: return
if (!recipient.isClosedGroupRecipient || !recipient.isLegacyClosedGroupRecipient) return
- val intent = Intent(this, EditClosedGroupActivity::class.java)
+ val intent = Intent(this, EditLegacyClosedGroupActivity::class.java)
val groupID: String = recipient.address.toGroupString()
- intent.putExtra(EditClosedGroupActivity.groupIDKey, groupID)
+ intent.putExtra(EditLegacyClosedGroupActivity.groupIDKey, groupID)
startActivity(intent)
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt
index 5dcfc8afd..b855e411a 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt
@@ -38,8 +38,8 @@ import org.thoughtcrime.securesms.contacts.SelectContactsActivity
import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2
import org.thoughtcrime.securesms.conversation.v2.utilities.NotificationUtils
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
-import org.thoughtcrime.securesms.groups.EditClosedGroupActivity
-import org.thoughtcrime.securesms.groups.EditClosedGroupActivity.Companion.groupIDKey
+import org.thoughtcrime.securesms.groups.EditLegacyClosedGroupActivity
+import org.thoughtcrime.securesms.groups.EditLegacyClosedGroupActivity.Companion.groupIDKey
import org.thoughtcrime.securesms.preferences.PrivacySettingsActivity
import org.thoughtcrime.securesms.service.WebRtcCallService
import org.thoughtcrime.securesms.showSessionDialog
@@ -281,7 +281,7 @@ object ConversationMenuHelper {
private fun editClosedGroup(context: Context, thread: Recipient) {
if (!thread.isLegacyClosedGroupRecipient) { return }
- val intent = Intent(context, EditClosedGroupActivity::class.java)
+ val intent = Intent(context, EditLegacyClosedGroupActivity::class.java)
val groupID: String = thread.address.toGroupString()
intent.putExtra(groupIDKey, groupID)
context.startActivity(intent)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
index a3b4ca64a..31f153d86 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt
@@ -1251,6 +1251,9 @@ open class Storage(
}
}
+ override fun getLibSessionClosedGroup(groupSessionId: String) =
+ configFactory.userGroups?.getClosedGroup(groupSessionId)
+
override fun setServerCapabilities(server: String, capabilities: List) {
return DatabaseComponent.get(context).lokiAPIDatabase().setServerCapabilities(server, capabilities)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt
index 9fee8adaf..753656cc7 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupActivity.kt
@@ -1,342 +1,30 @@
package org.thoughtcrime.securesms.groups
-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
-import android.view.inputmethod.InputMethodManager
-import android.widget.EditText
-import android.widget.LinearLayout
-import android.widget.TextView
-import android.widget.Toast
-import androidx.loader.app.LoaderManager
-import androidx.loader.content.Loader
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
+import androidx.activity.compose.setContent
+import androidx.lifecycle.viewmodel.viewModelFactory
+import com.ramcosta.composedestinations.DestinationsNavHost
+import com.ramcosta.composedestinations.navigation.dependency
import dagger.hilt.android.AndroidEntryPoint
-import network.loki.messenger.R
-import nl.komponents.kovenant.Promise
-import nl.komponents.kovenant.task
-import nl.komponents.kovenant.ui.failUi
-import nl.komponents.kovenant.ui.successUi
-import org.session.libsession.messaging.sending_receiving.MessageSender
-import org.session.libsession.messaging.sending_receiving.groupSizeLimit
-import org.session.libsession.utilities.Address
-import org.session.libsession.utilities.GroupUtil
-import org.session.libsession.utilities.TextSecurePreferences
-import org.session.libsession.utilities.ThemeUtil
-import org.session.libsession.utilities.recipients.Recipient
-import org.session.libsignal.utilities.Log
-import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
-import org.thoughtcrime.securesms.contacts.SelectContactsActivity
-import org.thoughtcrime.securesms.database.Storage
-import org.thoughtcrime.securesms.dependencies.ConfigFactory
-import org.thoughtcrime.securesms.dependencies.DatabaseComponent
-import org.thoughtcrime.securesms.groups.ClosedGroupManager.updateLegacyGroup
-import org.thoughtcrime.securesms.mms.GlideApp
-import org.thoughtcrime.securesms.util.fadeIn
-import org.thoughtcrime.securesms.util.fadeOut
-import java.io.IOException
-import javax.inject.Inject
+import org.thoughtcrime.securesms.groups.compose.EditGroupViewModel
+import org.thoughtcrime.securesms.ui.AppTheme
@AndroidEntryPoint
-class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
+class EditClosedGroupActivity: PassphraseRequiredActionBarActivity() {
- @Inject
- lateinit var groupConfigFactory: ConfigFactory
- @Inject
- lateinit var storage: Storage
-
- private val originalMembers = HashSet()
- private val zombies = HashSet()
- private val members = HashSet()
- private val allMembers: Set
- get() {
- return members + zombies
- }
- private var hasNameChanged = false
- private var isSelfAdmin = false
- private var isLoading = false
- set(newValue) { field = newValue; invalidateOptionsMenu() }
-
- private lateinit var groupID: String
- private lateinit var originalName: String
- private lateinit var name: String
-
- private var isEditingName = false
- set(value) {
- if (field == value) return
- field = value
- handleIsEditingNameChanged()
- }
-
- private val memberListAdapter by lazy {
- if (isSelfAdmin)
- EditClosedGroupMembersAdapter(this, GlideApp.with(this), isSelfAdmin, this::onMemberClick)
- else
- EditClosedGroupMembersAdapter(this, GlideApp.with(this), isSelfAdmin)
- }
-
- 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
-
- 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)
- setContentView(R.layout.activity_edit_closed_group)
-
- supportActionBar!!.setHomeAsUpIndicator(
- ThemeUtil.getThemedDrawableResId(this, R.attr.actionModeCloseDrawable))
-
- groupID = intent.getStringExtra(groupIDKey)!!
- val groupInfo = DatabaseComponent.get(this).groupDatabase().getGroup(groupID).get()
- originalName = groupInfo.title
- isSelfAdmin = groupInfo.admins.any{ it.serialize() == TextSecurePreferences.getLocalNumber(this) }
-
- 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)
-
- findViewById(R.id.addMembersClosedGroupButton).setOnClickListener {
- onAddMembersClick()
- }
-
- findViewById(R.id.rvUserList).apply {
- adapter = memberListAdapter
- layoutManager = LinearLayoutManager(this@EditClosedGroupActivity)
- }
-
- lblGroupNameDisplay.text = originalName
- cntGroupNameDisplay.setOnClickListener { isEditingName = true }
- findViewById(R.id.btnCancelGroupNameEdit).setOnClickListener { isEditingName = false }
- findViewById(R.id.btnSaveGroupNameEdit).setOnClickListener { saveName() }
- edtGroupName.setImeActionLabel(getString(R.string.save), EditorInfo.IME_ACTION_DONE)
- edtGroupName.setOnEditorActionListener { _, actionId, _ ->
- when (actionId) {
- EditorInfo.IME_ACTION_DONE -> {
- saveName()
- return@setOnEditorActionListener true
- }
- else -> return@setOnEditorActionListener false
- }
- }
-
- LoaderManager.getInstance(this).initLoader(loaderID, null, object : LoaderManager.LoaderCallbacks {
-
- override fun onCreateLoader(id: Int, bundle: Bundle?): Loader {
- return EditClosedGroupLoader(this@EditClosedGroupActivity, groupID)
- }
-
- override fun onLoadFinished(loader: Loader, groupMembers: GroupMembers) {
- // We no longer need any subsequent loading events
- // (they will occur on every activity resume).
- LoaderManager.getInstance(this@EditClosedGroupActivity).destroyLoader(loaderID)
-
- members.clear()
- members.addAll(groupMembers.members.toHashSet())
- zombies.clear()
- zombies.addAll(groupMembers.zombieMembers.toHashSet())
- originalMembers.clear()
- originalMembers.addAll(members + zombies)
- updateMembers()
- }
-
- override fun onLoaderReset(loader: Loader) {
- updateMembers()
- }
- })
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.menu_edit_closed_group, menu)
- return allMembers.isNotEmpty() && !isLoading
- }
- // endregion
-
- // region Updating
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- when (requestCode) {
- addUsersRequestCode -> {
- 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()
- }
- }
- }
-
- 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
-
- invalidateOptionsMenu()
- }
- // endregion
-
- // region Interaction
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.action_apply -> if (!isLoading) { commitChanges() }
- }
- return super.onOptionsItemSelected(item)
- }
-
- private fun onMemberClick(member: String) {
- val bottomSheet = ClosedGroupEditingOptionsBottomSheet()
- bottomSheet.onRemoveTapped = {
- if (zombies.contains(member)) zombies.remove(member)
- else members.remove(member)
- updateMembers()
- bottomSheet.dismiss()
- }
- bottomSheet.show(supportFragmentManager, "GroupEditingOptionsBottomSheet")
- }
-
- private fun onAddMembersClick() {
- val intent = Intent(this@EditClosedGroupActivity, SelectContactsActivity::class.java)
- intent.putExtra(SelectContactsActivity.usersToExcludeKey, allMembers.toTypedArray())
- intent.putExtra(SelectContactsActivity.emptyStateTextKey, "No contacts to add")
- startActivityForResult(intent, addUsersRequestCode)
- }
-
- 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()
- }
- if (name.length >= 64) {
- return Toast.makeText(this, R.string.activity_edit_closed_group_group_name_too_long_error, Toast.LENGTH_SHORT).show()
- }
- this.name = name
- lblGroupNameDisplay.text = name
- hasNameChanged = true
- isEditingName = false
- }
-
- private fun commitChanges() {
- val hasMemberListChanges = (allMembers != originalMembers)
-
- if (!hasNameChanged && !hasMemberListChanges) {
- return finish()
- }
-
- val name = if (hasNameChanged) this.name else originalName
-
- val members = this.allMembers.map {
- Recipient.from(this, Address.fromSerialized(it), false)
- }.toSet()
- val originalMembers = this.originalMembers.map {
- Recipient.from(this, Address.fromSerialized(it), false)
- }.toSet()
-
- var isClosedGroup: Boolean
- var groupPublicKey: String?
- try {
- groupPublicKey = GroupUtil.doubleDecodeGroupID(groupID).toHexString()
- isClosedGroup = DatabaseComponent.get(this).lokiAPIDatabase().isClosedGroup(groupPublicKey)
- } catch (e: IOException) {
- groupPublicKey = null
- isClosedGroup = false
- }
-
- if (members.isEmpty()) {
- return Toast.makeText(this, R.string.activity_edit_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
- }
-
- val maxGroupMembers = if (isClosedGroup) groupSizeLimit else legacyGroupSizeLimit
- if (members.size >= maxGroupMembers) {
- return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
- }
-
- val userPublicKey = TextSecurePreferences.getLocalNumber(this)!!
- val userAsRecipient = Recipient.from(this, Address.fromSerialized(userPublicKey), false)
-
- if (!members.contains(userAsRecipient) && !members.map { it.address.toString() }.containsAll(originalMembers.minus(userPublicKey))) {
- val message = "Can't leave while adding or removing other members."
- return Toast.makeText(this@EditClosedGroupActivity, message, Toast.LENGTH_LONG).show()
- }
-
- if (isClosedGroup) {
- isLoading = true
- loaderContainer.fadeIn()
- val promise: Promise = if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) {
- MessageSender.explicitLeave(groupPublicKey!!, false)
- } else {
- task {
- if (hasNameChanged) {
- MessageSender.explicitNameChange(groupPublicKey!!, name)
+ override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
+ setContent {
+ AppTheme {
+ DestinationsNavHost(
+ navGraph = NavGraphs.editGroup,
+ dependenciesContainerBuilder = {
+ dependency(NavGraphs.editGroup) {
+ EditGroupViewModel.Factory()
+ }
}
- members.filterNot { it in originalMembers }.let { adds ->
- if (adds.isNotEmpty()) MessageSender.explicitAddMembers(groupPublicKey!!, adds.map { it.address.serialize() })
- }
- originalMembers.filterNot { it in members }.let { removes ->
- if (removes.isNotEmpty()) MessageSender.explicitRemoveMembers(groupPublicKey!!, removes.map { it.address.serialize() })
- }
- }
- }
- promise.successUi {
- loaderContainer.fadeOut()
- isLoading = false
- updateGroupConfig()
- finish()
- }.failUi { exception ->
- 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
+ )
}
}
}
-
- private fun updateGroupConfig() {
- val latestRecipient = storage.getRecipientSettings(Address.fromSerialized(groupID))
- ?: return Log.w("Loki", "No recipient settings when trying to update group config")
- val latestGroup = storage.getGroup(groupID)
- ?: return Log.w("Loki", "No group record when trying to update group config")
- groupConfigFactory.updateLegacyGroup(latestRecipient, latestGroup)
- }
-
- class GroupMembers(val members: List, val zombieMembers: List)
}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupLoader.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupLoader.kt
index b1e0b5e1d..2acdfcd78 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupLoader.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/EditClosedGroupLoader.kt
@@ -4,13 +4,13 @@ import android.content.Context
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.util.AsyncLoader
-class EditClosedGroupLoader(context: Context, val groupID: String) : AsyncLoader(context) {
+class EditClosedGroupLoader(context: Context, val groupID: String) : AsyncLoader(context) {
- override fun loadInBackground(): EditClosedGroupActivity.GroupMembers {
+ override fun loadInBackground(): EditLegacyClosedGroupActivity.GroupMembers {
val groupDatabase = DatabaseComponent.get(context).groupDatabase()
val members = groupDatabase.getGroupMembers(groupID, true)
val zombieMembers = groupDatabase.getGroupZombieMembers(groupID)
- return EditClosedGroupActivity.GroupMembers(
+ return EditLegacyClosedGroupActivity.GroupMembers(
members.map {
it.address.toString()
},
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/EditLegacyClosedGroupActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/EditLegacyClosedGroupActivity.kt
new file mode 100644
index 000000000..fb572f0f0
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/EditLegacyClosedGroupActivity.kt
@@ -0,0 +1,342 @@
+package org.thoughtcrime.securesms.groups
+
+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
+import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
+import android.widget.LinearLayout
+import android.widget.TextView
+import android.widget.Toast
+import androidx.loader.app.LoaderManager
+import androidx.loader.content.Loader
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import dagger.hilt.android.AndroidEntryPoint
+import network.loki.messenger.R
+import nl.komponents.kovenant.Promise
+import nl.komponents.kovenant.task
+import nl.komponents.kovenant.ui.failUi
+import nl.komponents.kovenant.ui.successUi
+import org.session.libsession.messaging.sending_receiving.MessageSender
+import org.session.libsession.messaging.sending_receiving.groupSizeLimit
+import org.session.libsession.utilities.Address
+import org.session.libsession.utilities.GroupUtil
+import org.session.libsession.utilities.TextSecurePreferences
+import org.session.libsession.utilities.ThemeUtil
+import org.session.libsession.utilities.recipients.Recipient
+import org.session.libsignal.utilities.Log
+import org.session.libsignal.utilities.toHexString
+import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
+import org.thoughtcrime.securesms.contacts.SelectContactsActivity
+import org.thoughtcrime.securesms.database.Storage
+import org.thoughtcrime.securesms.dependencies.ConfigFactory
+import org.thoughtcrime.securesms.dependencies.DatabaseComponent
+import org.thoughtcrime.securesms.groups.ClosedGroupManager.updateLegacyGroup
+import org.thoughtcrime.securesms.mms.GlideApp
+import org.thoughtcrime.securesms.util.fadeIn
+import org.thoughtcrime.securesms.util.fadeOut
+import java.io.IOException
+import javax.inject.Inject
+
+@AndroidEntryPoint
+class EditLegacyClosedGroupActivity : PassphraseRequiredActionBarActivity() {
+
+ @Inject
+ lateinit var groupConfigFactory: ConfigFactory
+ @Inject
+ lateinit var storage: Storage
+
+ private val originalMembers = HashSet()
+ private val zombies = HashSet()
+ private val members = HashSet()
+ private val allMembers: Set
+ get() {
+ return members + zombies
+ }
+ private var hasNameChanged = false
+ private var isSelfAdmin = false
+ private var isLoading = false
+ set(newValue) { field = newValue; invalidateOptionsMenu() }
+
+ private lateinit var groupID: String
+ private lateinit var originalName: String
+ private lateinit var name: String
+
+ private var isEditingName = false
+ set(value) {
+ if (field == value) return
+ field = value
+ handleIsEditingNameChanged()
+ }
+
+ private val memberListAdapter by lazy {
+ if (isSelfAdmin)
+ EditClosedGroupMembersAdapter(this, GlideApp.with(this), isSelfAdmin, this::onMemberClick)
+ else
+ EditClosedGroupMembersAdapter(this, GlideApp.with(this), isSelfAdmin)
+ }
+
+ 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
+
+ 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)
+ setContentView(R.layout.activity_edit_closed_group)
+
+ supportActionBar!!.setHomeAsUpIndicator(
+ ThemeUtil.getThemedDrawableResId(this, R.attr.actionModeCloseDrawable))
+
+ groupID = intent.getStringExtra(groupIDKey)!!
+ val groupInfo = DatabaseComponent.get(this).groupDatabase().getGroup(groupID).get()
+ originalName = groupInfo.title
+ isSelfAdmin = groupInfo.admins.any{ it.serialize() == TextSecurePreferences.getLocalNumber(this) }
+
+ 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)
+
+ findViewById(R.id.addMembersClosedGroupButton).setOnClickListener {
+ onAddMembersClick()
+ }
+
+ findViewById(R.id.rvUserList).apply {
+ adapter = memberListAdapter
+ layoutManager = LinearLayoutManager(this@EditLegacyClosedGroupActivity)
+ }
+
+ lblGroupNameDisplay.text = originalName
+ cntGroupNameDisplay.setOnClickListener { isEditingName = true }
+ findViewById(R.id.btnCancelGroupNameEdit).setOnClickListener { isEditingName = false }
+ findViewById(R.id.btnSaveGroupNameEdit).setOnClickListener { saveName() }
+ edtGroupName.setImeActionLabel(getString(R.string.save), EditorInfo.IME_ACTION_DONE)
+ edtGroupName.setOnEditorActionListener { _, actionId, _ ->
+ when (actionId) {
+ EditorInfo.IME_ACTION_DONE -> {
+ saveName()
+ return@setOnEditorActionListener true
+ }
+ else -> return@setOnEditorActionListener false
+ }
+ }
+
+ LoaderManager.getInstance(this).initLoader(loaderID, null, object : LoaderManager.LoaderCallbacks {
+
+ override fun onCreateLoader(id: Int, bundle: Bundle?): Loader {
+ return EditClosedGroupLoader(this@EditLegacyClosedGroupActivity, groupID)
+ }
+
+ override fun onLoadFinished(loader: Loader, groupMembers: GroupMembers) {
+ // We no longer need any subsequent loading events
+ // (they will occur on every activity resume).
+ LoaderManager.getInstance(this@EditLegacyClosedGroupActivity).destroyLoader(loaderID)
+
+ members.clear()
+ members.addAll(groupMembers.members.toHashSet())
+ zombies.clear()
+ zombies.addAll(groupMembers.zombieMembers.toHashSet())
+ originalMembers.clear()
+ originalMembers.addAll(members + zombies)
+ updateMembers()
+ }
+
+ override fun onLoaderReset(loader: Loader) {
+ updateMembers()
+ }
+ })
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu_edit_closed_group, menu)
+ return allMembers.isNotEmpty() && !isLoading
+ }
+ // endregion
+
+ // region Updating
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ when (requestCode) {
+ addUsersRequestCode -> {
+ 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()
+ }
+ }
+ }
+
+ 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
+
+ invalidateOptionsMenu()
+ }
+ // endregion
+
+ // region Interaction
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_apply -> if (!isLoading) { commitChanges() }
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ private fun onMemberClick(member: String) {
+ val bottomSheet = ClosedGroupEditingOptionsBottomSheet()
+ bottomSheet.onRemoveTapped = {
+ if (zombies.contains(member)) zombies.remove(member)
+ else members.remove(member)
+ updateMembers()
+ bottomSheet.dismiss()
+ }
+ bottomSheet.show(supportFragmentManager, "GroupEditingOptionsBottomSheet")
+ }
+
+ private fun onAddMembersClick() {
+ val intent = Intent(this@EditLegacyClosedGroupActivity, SelectContactsActivity::class.java)
+ intent.putExtra(SelectContactsActivity.usersToExcludeKey, allMembers.toTypedArray())
+ intent.putExtra(SelectContactsActivity.emptyStateTextKey, "No contacts to add")
+ startActivityForResult(intent, addUsersRequestCode)
+ }
+
+ 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()
+ }
+ if (name.length >= 64) {
+ return Toast.makeText(this, R.string.activity_edit_closed_group_group_name_too_long_error, Toast.LENGTH_SHORT).show()
+ }
+ this.name = name
+ lblGroupNameDisplay.text = name
+ hasNameChanged = true
+ isEditingName = false
+ }
+
+ private fun commitChanges() {
+ val hasMemberListChanges = (allMembers != originalMembers)
+
+ if (!hasNameChanged && !hasMemberListChanges) {
+ return finish()
+ }
+
+ val name = if (hasNameChanged) this.name else originalName
+
+ val members = this.allMembers.map {
+ Recipient.from(this, Address.fromSerialized(it), false)
+ }.toSet()
+ val originalMembers = this.originalMembers.map {
+ Recipient.from(this, Address.fromSerialized(it), false)
+ }.toSet()
+
+ var isClosedGroup: Boolean
+ var groupPublicKey: String?
+ try {
+ groupPublicKey = GroupUtil.doubleDecodeGroupID(groupID).toHexString()
+ isClosedGroup = DatabaseComponent.get(this).lokiAPIDatabase().isClosedGroup(groupPublicKey)
+ } catch (e: IOException) {
+ groupPublicKey = null
+ isClosedGroup = false
+ }
+
+ if (members.isEmpty()) {
+ return Toast.makeText(this, R.string.activity_edit_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
+ }
+
+ val maxGroupMembers = if (isClosedGroup) groupSizeLimit else legacyGroupSizeLimit
+ if (members.size >= maxGroupMembers) {
+ return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
+ }
+
+ val userPublicKey = TextSecurePreferences.getLocalNumber(this)!!
+ val userAsRecipient = Recipient.from(this, Address.fromSerialized(userPublicKey), false)
+
+ if (!members.contains(userAsRecipient) && !members.map { it.address.toString() }.containsAll(originalMembers.minus(userPublicKey))) {
+ val message = "Can't leave while adding or removing other members."
+ return Toast.makeText(this@EditLegacyClosedGroupActivity, message, Toast.LENGTH_LONG).show()
+ }
+
+ if (isClosedGroup) {
+ isLoading = true
+ loaderContainer.fadeIn()
+ val promise: Promise = if (!members.contains(Recipient.from(this, Address.fromSerialized(userPublicKey), false))) {
+ MessageSender.explicitLeave(groupPublicKey!!, false)
+ } else {
+ task {
+ if (hasNameChanged) {
+ MessageSender.explicitNameChange(groupPublicKey!!, name)
+ }
+ members.filterNot { it in originalMembers }.let { adds ->
+ if (adds.isNotEmpty()) MessageSender.explicitAddMembers(groupPublicKey!!, adds.map { it.address.serialize() })
+ }
+ originalMembers.filterNot { it in members }.let { removes ->
+ if (removes.isNotEmpty()) MessageSender.explicitRemoveMembers(groupPublicKey!!, removes.map { it.address.serialize() })
+ }
+ }
+ }
+ promise.successUi {
+ loaderContainer.fadeOut()
+ isLoading = false
+ updateGroupConfig()
+ finish()
+ }.failUi { exception ->
+ val message = if (exception is MessageSender.Error) exception.description else "An error occurred"
+ Toast.makeText(this@EditLegacyClosedGroupActivity, message, Toast.LENGTH_LONG).show()
+ loaderContainer.fadeOut()
+ isLoading = false
+ }
+ }
+ }
+
+ private fun updateGroupConfig() {
+ val latestRecipient = storage.getRecipientSettings(Address.fromSerialized(groupID))
+ ?: return Log.w("Loki", "No recipient settings when trying to update group config")
+ val latestGroup = storage.getGroup(groupID)
+ ?: return Log.w("Loki", "No group record when trying to update group config")
+ groupConfigFactory.updateLegacyGroup(latestRecipient, latestGroup)
+ }
+
+ class GroupMembers(val members: List, val zombieMembers: List)
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroup.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroup.kt
new file mode 100644
index 000000000..51ec75eb2
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroup.kt
@@ -0,0 +1,55 @@
+package org.thoughtcrime.securesms.groups.compose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import app.cash.molecule.RecompositionMode
+import app.cash.molecule.RecompositionMode.Immediate
+import app.cash.molecule.launchMolecule
+import com.google.android.gms.auth.api.signin.internal.Storage
+import com.ramcosta.composedestinations.annotation.Destination
+import com.ramcosta.composedestinations.navigation.DestinationsNavigator
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.consumeAsFlow
+import org.session.libsession.database.StorageProtocol
+import javax.inject.Inject
+
+@EditGroupNavGraph(start = true)
+@Composable
+@Destination
+fun EditClosedGroupScreen(
+ navigator: DestinationsNavigator,
+ viewModel: EditGroupViewModel
+) {
+
+}
+
+
+
+@HiltViewModel
+class EditGroupViewModel @Inject constructor(private val groupSessionId: String,
+ private val storage: StorageProtocol): ViewModel() {
+
+ val viewState = viewModelScope.launchMolecule(Immediate) {
+
+ val closedGroup = remember {
+// storage.getLibSessionClosedGroup()
+ }
+
+ }
+
+}
+
+data class EditGroupState(
+ val viewState: EditGroupViewState,
+ val eventSink: (Unit)->Unit
+)
+
+sealed class EditGroupViewState {
+ data object NoOp: EditGroupViewState()
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroupNavGraph.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroupNavGraph.kt
new file mode 100644
index 000000000..b3cd75fee
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/groups/compose/EditGroupNavGraph.kt
@@ -0,0 +1,8 @@
+package org.thoughtcrime.securesms.groups.compose
+
+import com.ramcosta.composedestinations.annotation.NavGraph
+
+@NavGraph
+annotation class EditGroupNavGraph(
+ val start: Boolean = false
+)
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_edit_closed_group.xml b/app/src/main/res/layout/activity_edit_closed_group.xml
index e8a892f18..298bdfcbd 100644
--- a/app/src/main/res/layout/activity_edit_closed_group.xml
+++ b/app/src/main/res/layout/activity_edit_closed_group.xml
@@ -4,7 +4,7 @@
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
- tools:context="org.thoughtcrime.securesms.groups.EditClosedGroupActivity">
+ tools:context="org.thoughtcrime.securesms.groups.EditLegacyClosedGroupActivity">
fun acceptClosedGroupInvite(groupId: SessionId, name: String, authData: ByteArray, invitingAdmin: SessionId)
fun setGroupInviteCompleteIfNeeded(approved: Boolean, invitee: String, closedGroup: SessionId)
+ fun getLibSessionClosedGroup(groupSessionId: String): GroupInfo.ClosedGroupInfo?
// Groups
fun getAllGroups(includeInactive: Boolean): List