diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8023000ff3..4ccde5b2d4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -132,6 +132,9 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/fragment_group_edit_bottom_sheet.xml b/res/layout/fragment_group_edit_bottom_sheet.xml
new file mode 100644
index 0000000000..3c56dfa5e6
--- /dev/null
+++ b/res/layout/fragment_group_edit_bottom_sheet.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/menu/menu_edit_closed_group.xml b/res/menu/menu_edit_closed_group.xml
new file mode 100644
index 0000000000..7ded42d025
--- /dev/null
+++ b/res/menu/menu_edit_closed_group.xml
@@ -0,0 +1,10 @@
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 10f2ca0e38..94dcb8bdb0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1769,6 +1769,22 @@
A closed group cannot have more than 10 members
One of the members of your group has an invalid Session ID
+ Edit Closed Group
+ Enter a new group name (optional)
+ Closed groups support up to 10 members and provide the same privacy protections as one-on-one sessions.
+ Edit members
+ Add members
+ Group name can\'t be empty
+ Please enter a shorter group name
+ Groups must have at least 2 group members
+ A closed group cannot have more than 10 members
+ 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
+
+ Remove user from group
+ Make this user a group admin
+
Join Open Group
Couldn\'t join group
Open Group URL
diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
index b3446d3c85..2c87524b90 100644
--- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java
@@ -154,6 +154,7 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil;
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel;
import org.thoughtcrime.securesms.logging.Log;
+import org.thoughtcrime.securesms.loki.activities.EditClosedGroupActivity;
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabaseDelegate;
@@ -1162,10 +1163,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void handleEditPushGroup() {
- 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();
+ Intent intent = new Intent(this, EditClosedGroupActivity.class);
+ 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/CreateClosedGroupAdapter.kt b/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupAdapter.kt
index cf7f76a037..fa69ef8afa 100644
--- a/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupAdapter.kt
+++ b/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupAdapter.kt
@@ -42,9 +42,4 @@ class CreateClosedGroupAdapter(private val context: Context) : RecyclerView.Adap
val index = members.indexOf(member)
notifyItemChanged(index)
}
-}
-
-interface MemberClickListener {
-
- fun onMemberClick(member: String)
}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt
new file mode 100644
index 0000000000..434ed3a5a8
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt
@@ -0,0 +1,184 @@
+package org.thoughtcrime.securesms.loki.activities
+
+import android.content.Intent
+import android.graphics.Bitmap
+import android.os.AsyncTask
+import android.os.Bundle
+import android.support.v4.app.LoaderManager
+import android.support.v4.content.Loader
+import android.support.v7.widget.LinearLayoutManager
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+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
+import kotlinx.android.synthetic.main.activity_linked_devices.recyclerView
+import kotlinx.android.synthetic.main.activity_settings.*
+import network.loki.messenger.R
+import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
+import org.thoughtcrime.securesms.conversation.ConversationActivity
+import org.thoughtcrime.securesms.database.Address
+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.mms.GlideApp
+import org.thoughtcrime.securesms.recipients.Recipient
+import org.thoughtcrime.securesms.util.TextSecurePreferences
+import org.whispersystems.libsignal.util.guava.Optional
+import java.lang.ref.WeakReference
+
+class EditClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberClickListener, LoaderManager.LoaderCallbacks> {
+ private var members = listOf()
+ set(value) { field = value; editClosedGroupAdapter.members = value }
+
+ private val editClosedGroupAdapter by lazy {
+ val result = EditClosedGroupAdapter(this)
+ result.glide = GlideApp.with(this)
+ result.memberClickListener = this
+ result
+ }
+ private var isEditingDisplayName = false
+ private val selectedMembers: Set
+ get() { return editClosedGroupAdapter.selectedMembers }
+
+ companion object {
+ public val createNewPrivateChatResultCode = 100
+ }
+
+ // region Lifecycle
+ override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
+ super.onCreate(savedInstanceState, isReady)
+ 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"
+ recyclerView.adapter = editClosedGroupAdapter
+ recyclerView.layoutManager = LinearLayoutManager(this)
+ addMembersClosedGroupButton.setOnClickListener { createNewPrivateChat() }
+ LoaderManager.getInstance(this).initLoader(0, null, this)
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menuInflater.inflate(R.menu.menu_edit_closed_group, menu)
+ return members.isNotEmpty()
+ }
+ // endregion
+
+ // region Updating
+ override fun onCreateLoader(id: Int, bundle: Bundle?): Loader> {
+ return CreateClosedGroupLoader(this)
+ }
+
+ override fun onLoadFinished(loader: Loader>, members: List) {
+ update(members)
+ }
+
+ override fun onLoaderReset(loader: Loader>) {
+ update(listOf())
+ }
+
+ private fun update(members: List) {
+ this.members = members
+ mainContentContainer.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE
+ emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE
+ invalidateOptionsMenu()
+ }
+ // endregion
+
+ // region Interaction
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ val id = item.itemId
+ when(id) {
+ R.id.editClosedGroupButton -> modifyClosedGroup()
+ else -> { /* Do nothing */ }
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ private fun createNewPrivateChat() {
+ setResult(createNewPrivateChatResultCode)
+ finish()
+ }
+ private fun showEditDisplayNameUI() {
+ isEditingDisplayName = true
+ }
+
+ override fun onMemberClick(member: String) {
+ val bottomSheet = GroupEditingOptionsBottomSheet()
+ bottomSheet.onRemoveTapped = {
+ bottomSheet.dismiss()
+ }
+ // bottomSheet.onAdminTapped = {
+ // bottomSheet.dismiss()
+ // }
+ 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)
+ intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId)
+ intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT)
+ intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address)
+ startActivity(intent)
+ finish()
+ }
+ // endregion
+
+ // region Tasks
+ internal class CreateClosedGroupTask(
+ private val activity: WeakReference,
+ private val profilePicture: Bitmap?,
+ private val name: String?,
+ private val members: Set,
+ private val admins: Set
+ ) : AsyncTask>() {
+
+ 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))
+ }
+
+ override fun onPostExecute(result: Optional) {
+ val activity = activity.get() ?: return super.onPostExecute(result)
+ if (result.isPresent && result.get().threadId > -1) {
+ if (!activity.isFinishing) {
+ activity.handleOpenConversation(result.get().threadId, result.get().groupRecipient)
+ }
+ } else {
+ super.onPostExecute(result)
+ Toast.makeText(activity.applicationContext, R.string.activity_create_closed_group_invalid_session_id_error, Toast.LENGTH_LONG).show()
+ }
+ }
+ }
+ // 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
new file mode 100644
index 0000000000..cc371a9122
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupAdapter.kt
@@ -0,0 +1,48 @@
+package org.thoughtcrime.securesms.loki.activities
+
+import android.R
+import android.app.PendingIntent.getActivity
+import android.content.Context
+import android.support.v7.app.AlertDialog
+import android.support.v7.widget.RecyclerView
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.EditText
+import android.widget.LinearLayout
+import org.thoughtcrime.securesms.DeviceListItem
+import org.thoughtcrime.securesms.database.Address
+import org.thoughtcrime.securesms.loki.dialogs.DeviceEditingOptionsBottomSheet
+import org.thoughtcrime.securesms.loki.dialogs.GroupEditingOptionsBottomSheet
+import org.thoughtcrime.securesms.loki.utilities.toPx
+import org.thoughtcrime.securesms.loki.views.UserView
+import org.thoughtcrime.securesms.mms.GlideRequests
+import org.thoughtcrime.securesms.recipients.Recipient
+
+class EditClosedGroupAdapter(private val context: Context) : RecyclerView.Adapter() {
+ lateinit var glide: GlideRequests
+ val selectedMembers = mutableSetOf()
+ var members = listOf()
+ set(value) {
+ field = value; notifyDataSetChanged()
+ }
+ var memberClickListener: MemberClickListener? = null
+
+ class ViewHolder(val view: UserView) : RecyclerView.ViewHolder(view)
+
+ override fun getItemCount(): Int {
+ return members.size
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view = UserView(context)
+ return ViewHolder(view)
+ }
+
+ override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
+ val member = members[position]
+ viewHolder.view.setOnClickListener { memberClickListener?.onMemberClick(member) }
+ val isSelected = selectedMembers.contains(member)
+ viewHolder.view.bind(Recipient.from(context, Address.fromSerialized(member), false), isSelected, glide)
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupLoader.kt b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupLoader.kt
new file mode 100644
index 0000000000..311bc1becd
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupLoader.kt
@@ -0,0 +1,18 @@
+package org.thoughtcrime.securesms.loki.activities
+
+import android.content.Context
+import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
+import org.thoughtcrime.securesms.util.AsyncLoader
+
+class EditClosedGroupLoader(context: Context) : AsyncLoader>(context) {
+
+ override fun loadInBackground(): 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
diff --git a/src/org/thoughtcrime/securesms/loki/activities/MemberClickListener.kt b/src/org/thoughtcrime/securesms/loki/activities/MemberClickListener.kt
new file mode 100644
index 0000000000..c1599ce408
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/activities/MemberClickListener.kt
@@ -0,0 +1,5 @@
+package org.thoughtcrime.securesms.loki.activities
+
+interface MemberClickListener {
+ fun onMemberClick(member: String)
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/dialogs/GroupEditingOptionsBottomSheet.kt b/src/org/thoughtcrime/securesms/loki/dialogs/GroupEditingOptionsBottomSheet.kt
new file mode 100644
index 0000000000..fe967ef652
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/dialogs/GroupEditingOptionsBottomSheet.kt
@@ -0,0 +1,24 @@
+package org.thoughtcrime.securesms.loki.dialogs
+
+import android.os.Bundle
+import android.support.design.widget.BottomSheetDialogFragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import kotlinx.android.synthetic.main.fragment_group_edit_bottom_sheet.*
+import network.loki.messenger.R
+
+public class GroupEditingOptionsBottomSheet : BottomSheetDialogFragment() {
+ var onRemoveTapped: (() -> Unit)? = null
+// var onAdminTapped: (() -> Unit)? = null
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+ return inflater.inflate(R.layout.fragment_group_edit_bottom_sheet, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ removeFromGroup.setOnClickListener { onRemoveTapped?.invoke() }
+// makeAdministrator.setOnClickListener { onAdminTapped?.invoke() }
+ }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/views/UserView.kt b/src/org/thoughtcrime/securesms/loki/views/UserView.kt
index 16ffc77db4..0ffb2c358f 100644
--- a/src/org/thoughtcrime/securesms/loki/views/UserView.kt
+++ b/src/org/thoughtcrime/securesms/loki/views/UserView.kt
@@ -53,7 +53,8 @@ class UserView : LinearLayout {
profilePictureView.isRSSFeed = true
} else {
val threadID = GroupManager.getThreadIDFromGroupID(address, context)
- val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toList() ?: listOf()
+ val users = MentionsManager.shared.userPublicKeyCache[threadID]?.toList()
+ ?: listOf()
val randomUsers = users.sorted() // Sort to provide a level of stability
profilePictureView.hexEncodedPublicKey = randomUsers.getOrNull(0) ?: ""
profilePictureView.additionalHexEncodedPublicKey = randomUsers.getOrNull(1) ?: ""
@@ -67,7 +68,12 @@ class UserView : LinearLayout {
profilePictureView.glide = glide
profilePictureView.update()
nameTextView.text = user.name ?: "Unknown Contact"
- tickImageView.setImageResource(if (isSelected) R.drawable.ic_circle_check else R.drawable.ic_circle)
+ if (user.isGroupRecipient) {
+ // this doesnt work, find out how to work out if the user is a part of the group)
+ tickImageView.setImageResource(R.drawable.ic_edit_white_24dp)
+ } else {
+ tickImageView.setImageResource(if (isSelected) R.drawable.ic_circle_check else R.drawable.ic_circle)
+ }
}
// endregion
}
\ No newline at end of file