diff --git a/res/layout/activity_edit_closed_group.xml b/res/layout/activity_edit_closed_group.xml index 7e54b5c52..49a8463ba 100644 --- a/res/layout/activity_edit_closed_group.xml +++ b/res/layout/activity_edit_closed_group.xml @@ -28,7 +28,7 @@ android:paddingTop="12dp" android:paddingBottom="@dimen/large_spacing" android:visibility="invisible" - android:hint="@string/activity_settings_display_name_edit_text_hint" /> + android:hint="@string/activity_edit_closed_group_edit_text_hint" /> diff --git a/res/values/strings.xml b/res/values/strings.xml index 94dcb8bdb..bc2001a3d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1770,7 +1770,7 @@ One of the members of your group has an invalid Session ID Edit Closed Group - Enter a new group name (optional) + Enter a new group name Closed groups support up to 10 members and provide the same privacy protections as one-on-one sessions. Edit members Add members @@ -1781,6 +1781,7 @@ One of the members of your group has an invalid Session ID Are you sure you want to remove this user? User removed from group + Apply Remove user from group Make this user a group admin diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 2c87524b9..bc98f2264 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -1164,11 +1164,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void handleEditPushGroup() { Intent intent = new Intent(this, EditClosedGroupActivity.class); + String groupID = this.recipient.getAddress().toGroupString(); + intent.putExtra(EditClosedGroupActivity.GROUP_ID, groupID); startActivity(intent); -// AlertDialog.Builder alert = new AlertDialog.Builder(this); -// alert.setMessage("The ability to add members to a closed group is coming soon."); -// alert.setPositiveButton("OK", (dialog, which) -> dialog.dismiss()); -// alert.create().show(); } private void handleDistributionBroadcastEnabled(MenuItem item) { diff --git a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt index e2f7b14bb..2e6574cef 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt @@ -12,12 +12,9 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.inputmethod.InputMethodManager -import android.widget.LinearLayout import android.widget.Toast -import kotlinx.android.synthetic.main.activity_create_closed_group.* import kotlinx.android.synthetic.main.activity_create_closed_group.emptyStateContainer import kotlinx.android.synthetic.main.activity_create_closed_group.mainContentContainer -import kotlinx.android.synthetic.main.activity_create_closed_group.nameEditText import kotlinx.android.synthetic.main.activity_edit_closed_group.* import kotlinx.android.synthetic.main.activity_edit_closed_group.displayNameContainer import kotlinx.android.synthetic.main.activity_edit_closed_group.displayNameTextView @@ -31,16 +28,22 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.loki.dialogs.GroupEditingOptionsBottomSheet -import org.thoughtcrime.securesms.loki.utilities.toPx import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.TextSecurePreferences import org.whispersystems.libsignal.util.guava.Optional +import org.whispersystems.signalservice.api.crypto.ProfileCipher import java.lang.ref.WeakReference class EditClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberClickListener, LoaderManager.LoaderCallbacks> { private var members = listOf() set(value) { field = value; editClosedGroupAdapter.members = value } + private lateinit var groupID: String + private var membersToRemove = setOf() + private var membersToAdd = setOf() + private var admins = setOf() + private var displayNameToBeUploaded: String? = null + private val originalName by lazy { DatabaseFactory.getGroupDatabase(this).getGroup(groupID).get().title } private val editClosedGroupAdapter by lazy { val result = EditClosedGroupAdapter(this) @@ -48,22 +51,25 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberCli result.memberClickListener = this result } - private var isEditingDisplayName = false + private var isEditingGroupName = false set(value) { field = value; handleIsEditingDisplayNameChanged() } - private val selectedMembers: Set - get() { return editClosedGroupAdapter.selectedMembers } companion object { public val createNewPrivateChatResultCode = 100 + @JvmField + public val GROUP_ID = "GROUP_ID" } // region Lifecycle override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { super.onCreate(savedInstanceState, isReady) + groupID = intent.getStringExtra(GROUP_ID) setContentView(R.layout.activity_edit_closed_group) supportActionBar!!.title = resources.getString(R.string.activity_edit_closed_group_title) displayNameContainer.setOnClickListener { showEditDisplayNameUI() } - displayNameTextView.text = "Get Group Name" // DatabaseFactory.getLokiUserDatabase(this).getDisplayName(hexEncodedPublicKey) + displayNameTextView.text = originalName + cancelEditButton.setOnClickListener { cancelEditingDisplayName() } + saveEditButton.setOnClickListener { saveDisplayName() } recyclerView.adapter = editClosedGroupAdapter recyclerView.layoutManager = LinearLayoutManager(this) addMembersClosedGroupButton.setOnClickListener { createNewPrivateChat() } @@ -78,7 +84,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberCli // region Updating override fun onCreateLoader(id: Int, bundle: Bundle?): Loader> { - return CreateClosedGroupLoader(this) + return EditClosedGroupLoader(groupID, this) } override fun onLoadFinished(loader: Loader>, members: List) { @@ -112,12 +118,17 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberCli finish() } private fun showEditDisplayNameUI() { - isEditingDisplayName = true + isEditingGroupName = true + } + private fun cancelEditingDisplayName() { + isEditingGroupName = false } override fun onMemberClick(member: String) { val bottomSheet = GroupEditingOptionsBottomSheet() bottomSheet.onRemoveTapped = { + membersToRemove = membersToRemove + member + members = members - member bottomSheet.dismiss() } // bottomSheet.onAdminTapped = { @@ -126,28 +137,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberCli bottomSheet.show(supportFragmentManager, "closeBottomSheet") } - private fun modifyClosedGroup() { - val name = nameEditText.text.trim() - if (name.isEmpty()) { - return Toast.makeText(this, R.string.activity_edit_closed_group_group_name_missing_error, Toast.LENGTH_LONG).show() - } - if (name.length >= 64) { - return Toast.makeText(this, R.string.activity_edit_closed_group_group_name_too_long_error, Toast.LENGTH_LONG).show() - } - if (selectedMembers.count() < 2) { - return Toast.makeText(this, R.string.activity_edit_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show() - } - if (selectedMembers.count() > 10) { - return Toast.makeText(this, R.string.activity_edit_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show() - } - val recipients = selectedMembers.map { - Recipient.from(this, Address.fromSerialized(it), false) - }.toSet() - val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this) ?: TextSecurePreferences.getLocalNumber(this) - val admin = Recipient.from(this, Address.fromSerialized(masterHexEncodedPublicKey), false) - CreateClosedGroupTask(WeakReference(this), null, name.toString(), recipients, setOf( admin )).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) - } private fun handleOpenConversation(threadId: Long, recipient: Recipient) { val intent = Intent(this, ConversationActivity::class.java) @@ -159,23 +149,55 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberCli } private fun handleIsEditingDisplayNameChanged() { - cancelEditButton.visibility = if (isEditingDisplayName) View.VISIBLE else View.GONE - saveEditButton.visibility = if (isEditingDisplayName) View.VISIBLE else View.GONE - displayNameTextView.visibility = if (isEditingDisplayName) View.INVISIBLE else View.VISIBLE - groupNameEditText.visibility = if (isEditingDisplayName) View.VISIBLE else View.INVISIBLE + cancelEditButton.visibility = if (isEditingGroupName) View.VISIBLE else View.GONE + saveEditButton.visibility = if (isEditingGroupName) View.VISIBLE else View.GONE + displayNameTextView.visibility = if (isEditingGroupName) View.INVISIBLE else View.VISIBLE + groupNameEditText.visibility = if (isEditingGroupName) View.VISIBLE else View.INVISIBLE val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - if (isEditingDisplayName) { + if (isEditingGroupName) { groupNameEditText.requestFocus() inputMethodManager.showSoftInput(groupNameEditText, 0) } else { inputMethodManager.hideSoftInputFromWindow(groupNameEditText.windowToken, 0) } } + + private fun saveDisplayName() { + val groupDisplayName = displayNameEditText.text.toString().trim() + if (groupDisplayName.isEmpty()) { + return Toast.makeText(this, R.string.activity_settings_display_name_missing_error, Toast.LENGTH_SHORT).show() + } + if (!groupDisplayName.matches(Regex("[a-zA-Z0-9_]+"))) { + return Toast.makeText(this, R.string.activity_settings_invalid_display_name_error, Toast.LENGTH_SHORT).show() + } + if (groupDisplayName.toByteArray().size > ProfileCipher.NAME_PADDED_LENGTH) { + return Toast.makeText(this, R.string.activity_settings_display_name_too_long_error, Toast.LENGTH_SHORT).show() + } + isEditingGroupName = false + displayNameToBeUploaded = groupDisplayName + } + private fun modifyClosedGroup() { + if (originalName == displayNameToBeUploaded && membersToRemove.isEmpty() && membersToAdd.isEmpty()) { /* do nothing, close the activity and return to conversation */ } else { + var groupDisplayName = originalName + if (originalName != displayNameToBeUploaded) { + groupDisplayName = displayNameToBeUploaded.toString() + } + val finalGroupMembers = members.map { + Recipient.from(this, Address.fromSerialized(it), false) + }.toSet() + val finalGroupAdmins = admins.map { + Recipient.from(this, Address.fromSerialized(it), false) + }.toSet() + GroupManager.updateGroup(this, groupID, finalGroupMembers, null, groupDisplayName, finalGroupAdmins) + finish() + } + } // endregion // region Tasks - internal class CreateClosedGroupTask( + internal class EditClosedGroupTask( private val activity: WeakReference, + private val groupID: String, private val profilePicture: Bitmap?, private val name: String?, private val members: Set, @@ -184,7 +206,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberCli override fun doInBackground(vararg params: Void?): Optional { val activity = activity.get() ?: return Optional.absent() - return Optional.of(GroupManager.createGroup(activity, members, profilePicture, name, false, admins)) + return Optional.of(GroupManager.updateGroup(activity, groupID, members, profilePicture, name, admins)) } override fun onPostExecute(result: Optional) { @@ -199,5 +221,4 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberCli } } } - // endregion } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupAdapter.kt b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupAdapter.kt index cc371a912..eb94f35c2 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupAdapter.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupAdapter.kt @@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.recipients.Recipient class EditClosedGroupAdapter(private val context: Context) : RecyclerView.Adapter() { lateinit var glide: GlideRequests - val selectedMembers = mutableSetOf() + private val selectedMembers = mutableSetOf() var members = listOf() set(value) { field = value; notifyDataSetChanged() diff --git a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupLoader.kt b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupLoader.kt index 311bc1bec..8b8bf276c 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupLoader.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupLoader.kt @@ -1,12 +1,22 @@ package org.thoughtcrime.securesms.loki.activities import android.content.Context +import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.loki.utilities.ContactUtilities import org.thoughtcrime.securesms.util.AsyncLoader -class EditClosedGroupLoader(context: Context) : AsyncLoader>(context) { +class EditClosedGroupLoader(val groupID: String, context: Context) : AsyncLoader>(context) { override fun loadInBackground(): List { + val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, false) + return members.map { + it.address.toPhoneString() + } + } + +/* For loading contacts for Add members, and loading admins from group list + + override fun loadContactsInBackground(): List { val contacts = ContactUtilities.getAllContacts(context) // Only show the master devices of the users we are friends with return contacts.filter { contact -> @@ -15,4 +25,15 @@ class EditClosedGroupLoader(context: Context) : AsyncLoader>(contex it.recipient.address.toPhoneString() } } + override fun loadAdminsInBackground(): List { + val contacts = ContactUtilities.getAllContacts(context) + // Only show the master devices of the users we are friends with + return contacts.filter { contact -> + !contact.recipient.isGroupRecipient && contact.isFriend && !contact.isOurDevice && !contact.isSlave + }.map { + it.recipient.address.toPhoneString() + } + } + + */ } \ No newline at end of file