diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e4693b99ed..0e5a5d0654 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -158,6 +158,9 @@
+
+
+
diff --git a/res/layout/activity_create_closed_group.xml b/res/layout/activity_create_closed_group.xml
new file mode 100644
index 0000000000..fa6585f797
--- /dev/null
+++ b/res/layout/activity_create_closed_group.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/activity_home.xml b/res/layout/activity_home.xml
index d03b9cb65d..28e927d94d 100644
--- a/res/layout/activity_home.xml
+++ b/res/layout/activity_home.xml
@@ -40,13 +40,27 @@
android:layout_centerVertical="true"
android:layout_marginLeft="64dp" />
-
+ android:layout_centerVertical="true">
+
+
+
+
+
+
diff --git a/res/layout/view_user.xml b/res/layout/view_user.xml
new file mode 100644
index 0000000000..6c63b7759c
--- /dev/null
+++ b/res/layout/view_user.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/menu/menu_create_closed_group.xml b/res/menu/menu_create_closed_group.xml
new file mode 100644
index 0000000000..75b41dcd68
--- /dev/null
+++ b/res/menu/menu_create_closed_group.xml
@@ -0,0 +1,10 @@
+
+
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/CreateClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/CreateClosedGroupActivity.kt
new file mode 100644
index 0000000000..056d8b7296
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/CreateClosedGroupActivity.kt
@@ -0,0 +1,93 @@
+package org.thoughtcrime.securesms.loki.redesign.activities
+
+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.widget.Toast
+import kotlinx.android.synthetic.main.activity_linked_devices.*
+import kotlinx.android.synthetic.main.view_user.*
+import network.loki.messenger.R
+import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
+import org.thoughtcrime.securesms.mms.GlideApp
+
+class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberClickListener, LoaderManager.LoaderCallbacks> {
+ private var members = listOf()
+ set(value) { field = value; createClosedGroupAdapter.members = value }
+
+ private val createClosedGroupAdapter by lazy {
+ val result = CreateClosedGroupAdapter(this)
+ result.glide = GlideApp.with(this)
+ result.memberClickListener = this
+ result
+ }
+
+ private val selectedMembers: Set
+ get() { return createClosedGroupAdapter.selectedMembers }
+
+ // region Lifecycle
+ override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
+ super.onCreate(savedInstanceState, isReady)
+ setContentView(R.layout.activity_create_closed_group)
+ supportActionBar!!.title = "New Closed Group"
+ recyclerView.adapter = createClosedGroupAdapter
+ recyclerView.layoutManager = LinearLayoutManager(this)
+ LoaderManager.getInstance(this).initLoader(0, null, this)
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menuInflater.inflate(R.menu.menu_create_closed_group, menu)
+ return true
+ }
+ // 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
+ }
+ // endregion
+
+ // region Interaction
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ val id = item.itemId
+ when(id) {
+ R.id.createClosedGroupButton -> createClosedGroup()
+ else -> { /* Do nothing */ }
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ override fun onMemberClick(member: String) {
+ createClosedGroupAdapter.onMemberClick(member)
+ }
+
+ private fun createClosedGroup() {
+ val name = nameTextView.text.trim()
+ if (name.isEmpty()) {
+ return Toast.makeText(this, "Please enter a group name", Toast.LENGTH_LONG).show()
+ }
+ if (name.length >= 64) {
+ return Toast.makeText(this, "Please enter a shorter group name", Toast.LENGTH_LONG).show()
+ }
+ val selectedMembers = this.selectedMembers
+ if (selectedMembers.count() < 2) {
+ return Toast.makeText(this, "Please pick at least 2 group members", Toast.LENGTH_LONG).show()
+ }
+ // TODO: Create group
+ }
+ // endregion
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/CreateClosedGroupAdapter.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/CreateClosedGroupAdapter.kt
new file mode 100644
index 0000000000..bd8c6eff02
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/CreateClosedGroupAdapter.kt
@@ -0,0 +1,48 @@
+package org.thoughtcrime.securesms.loki.redesign.activities
+
+import android.content.Context
+import android.support.v7.widget.RecyclerView
+import android.view.ViewGroup
+import org.thoughtcrime.securesms.loki.redesign.views.UserView
+import org.thoughtcrime.securesms.mms.GlideRequests
+
+class CreateClosedGroupAdapter(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(member, isSelected, glide)
+ }
+
+ fun onMemberClick(member: String) {
+ if (selectedMembers.contains(member)) {
+ selectedMembers.remove(member)
+ } else {
+ selectedMembers.add(member)
+ }
+ 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/redesign/activities/CreateClosedGroupLoader.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/CreateClosedGroupLoader.kt
new file mode 100644
index 0000000000..620d47b5c6
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/CreateClosedGroupLoader.kt
@@ -0,0 +1,33 @@
+package org.thoughtcrime.securesms.loki.redesign.activities
+
+import android.content.Context
+import org.thoughtcrime.securesms.database.DatabaseFactory
+import org.thoughtcrime.securesms.util.AsyncLoader
+import org.thoughtcrime.securesms.util.TextSecurePreferences
+import org.whispersystems.signalservice.loki.messaging.LokiThreadFriendRequestStatus
+
+class CreateClosedGroupLoader(context: Context) : AsyncLoader>(context) {
+
+ override fun loadInBackground(): List {
+ val threadDatabase = DatabaseFactory.getThreadDatabase(context)
+ val lokiThreadDatabase = DatabaseFactory.getLokiThreadDatabase(context)
+ val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context)
+ val deviceLinks = DatabaseFactory.getLokiAPIDatabase(context).getPairingAuthorisations(userHexEncodedPublicKey)
+ val userLinkedDeviceHexEncodedPublicKeys = deviceLinks.flatMap {
+ listOf( it.primaryDevicePublicKey.toLowerCase(), it.secondaryDevicePublicKey.toLowerCase() )
+ }.toMutableSet()
+ userLinkedDeviceHexEncodedPublicKeys.add(userHexEncodedPublicKey.toLowerCase())
+ val cursor = threadDatabase.conversationList
+ val reader = threadDatabase.readerFor(cursor)
+ val result = mutableListOf()
+ while (reader.next != null) {
+ val thread = reader.current
+ if (thread.recipient.isGroupRecipient) { continue }
+ if (lokiThreadDatabase.getFriendRequestStatus(thread.threadId) != LokiThreadFriendRequestStatus.FRIENDS) { continue }
+ val hexEncodedPublicKey = thread.recipient.address.toString().toLowerCase()
+ if (userLinkedDeviceHexEncodedPublicKeys.contains(hexEncodedPublicKey)) { continue }
+ result.add(hexEncodedPublicKey)
+ }
+ return result
+ }
+}
\ No newline at end of file
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt
index 798528dbbe..bb24b04dac 100644
--- a/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt
@@ -83,6 +83,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
profileButton.hexEncodedPublicKey = hexEncodedPublicKey
profileButton.update()
profileButton.setOnClickListener { openSettings() }
+ createClosedGroupButton.setOnClickListener { createClosedGroup() }
joinPublicChatButton.setOnClickListener { joinPublicChat() }
// Set up seed reminder view
val isMasterDevice = (TextSecurePreferences.getMasterHexEncodedPublicKey(this) == null)
@@ -182,6 +183,11 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
show(intent)
}
+ private fun createClosedGroup() {
+ val intent = Intent(this, CreateClosedGroupActivity::class.java)
+ show(intent)
+ }
+
private fun joinPublicChat() {
val intent = Intent(this, JoinPublicChatActivity::class.java)
show(intent)
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/views/UserView.kt b/src/org/thoughtcrime/securesms/loki/redesign/views/UserView.kt
new file mode 100644
index 0000000000..2b6e6536f7
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/loki/redesign/views/UserView.kt
@@ -0,0 +1,52 @@
+package org.thoughtcrime.securesms.loki.redesign.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.LinearLayout
+import kotlinx.android.synthetic.main.view_conversation.view.profilePictureView
+import kotlinx.android.synthetic.main.view_user.view.*
+import network.loki.messenger.R
+import org.thoughtcrime.securesms.database.Address
+import org.thoughtcrime.securesms.mms.GlideRequests
+import org.thoughtcrime.securesms.recipients.Recipient
+
+class UserView : LinearLayout {
+ var user: String? = null
+
+ // region Lifecycle
+ constructor(context: Context) : super(context) {
+ setUpViewHierarchy()
+ }
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
+ setUpViewHierarchy()
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
+ setUpViewHierarchy()
+ }
+
+ constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
+ setUpViewHierarchy()
+ }
+
+ private fun setUpViewHierarchy() {
+ val inflater = context.applicationContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+ val contentView = inflater.inflate(R.layout.view_user, null)
+ addView(contentView)
+ }
+ // endregion
+
+ // region Updating
+ fun bind(user: String, isSelected: Boolean, glide: GlideRequests) {
+ profilePictureView.hexEncodedPublicKey = user
+ profilePictureView.additionalHexEncodedPublicKey = null
+ profilePictureView.isRSSFeed = false
+ profilePictureView.glide = glide
+ profilePictureView.update()
+ nameTextView.text = Recipient.from(context, Address.fromSerialized(user), false).name ?: "Unknown Contact"
+ tickImageView.setImageResource(if (isSelected) R.drawable.ic_circle_check else R.drawable.ic_circle)
+ }
+ // endregion
+}
\ No newline at end of file