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