Merge branch 'dev' into light-theme

This commit is contained in:
Anton Chekulaev
2020-08-19 11:49:49 +10:00
778 changed files with 2562 additions and 1979 deletions

View File

@@ -1,12 +1,13 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Context
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 androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import android.view.View
@@ -27,44 +28,45 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.libsignal.util.guava.Optional
import java.lang.ref.WeakReference
class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberClickListener, LoaderManager.LoaderCallbacks<List<String>> {
class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> {
private var members = listOf<String>()
set(value) { field = value; createClosedGroupAdapter.members = value }
set(value) {
field = value
selectContactsAdapter.members = value
}
private val createClosedGroupAdapter by lazy {
val result = CreateClosedGroupAdapter(this)
result.glide = GlideApp.with(this)
result.memberClickListener = this
result
private val selectContactsAdapter by lazy {
SelectContactsAdapter(this, GlideApp.with(this))
}
private val selectedMembers: Set<String>
get() { return createClosedGroupAdapter.selectedMembers }
companion object {
public val createNewPrivateChatResultCode = 100
val closedGroupCreatedResultCode = 100
}
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
setContentView(R.layout.activity_create_closed_group)
supportActionBar!!.title = resources.getString(R.string.activity_create_closed_group_title)
recyclerView.adapter = createClosedGroupAdapter
recyclerView.adapter = this.selectContactsAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() }
btnCreateNewPrivateChat.setOnClickListener { createNewPrivateChat() }
LoaderManager.getInstance(this).initLoader(0, null, this)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_create_closed_group, menu)
menuInflater.inflate(R.menu.menu_done, menu)
return members.isNotEmpty()
}
// endregion
// region Updating
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<List<String>> {
return CreateClosedGroupLoader(this)
return SelectContactsLoader(this, setOf())
}
override fun onLoadFinished(loader: Loader<List<String>>, members: List<String>) {
@@ -85,23 +87,17 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
// region Interaction
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
when(id) {
R.id.createClosedGroupButton -> createClosedGroup()
else -> { /* Do nothing */ }
when(item.itemId) {
R.id.doneButton -> createClosedGroup()
}
return super.onOptionsItemSelected(item)
}
private fun createNewPrivateChat() {
setResult(createNewPrivateChatResultCode)
setResult(Companion.closedGroupCreatedResultCode)
finish()
}
override fun onMemberClick(member: String) {
createClosedGroupAdapter.onMemberClick(member)
}
private fun createClosedGroup() {
if (ClosedGroupsProtocol.isSharedSenderKeysEnabled) {
createSSKBasedClosedGroup()
@@ -118,17 +114,17 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
if (name.length >= 64) {
return Toast.makeText(this, R.string.activity_create_closed_group_group_name_too_long_error, Toast.LENGTH_LONG).show()
}
val selectedMembers = this.selectedMembers
val selectedMembers = this.selectContactsAdapter.selectedMembers
if (selectedMembers.count() < 2) {
return Toast.makeText(this, R.string.activity_create_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
}
if (selectedMembers.count() > 49) { // Minus one because we're going to include self later
if (selectedMembers.count() > ClosedGroupsProtocol.groupSizeLimit) { // Minus one because we're going to include self later
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 groupID = ClosedGroupsProtocol.createClosedGroup(this, name.toString(), selectedMembers + setOf( userPublicKey ))
val threadID = DatabaseFactory.getThreadDatabase(this).getThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false))
openConversation(threadID, Recipient.from(this, Address.fromSerialized(groupID), false))
openConversationActivity(this, threadID, Recipient.from(this, Address.fromSerialized(groupID), false))
}
private fun createLegacyClosedGroup() {
@@ -139,7 +135,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
if (name.length >= 64) {
return Toast.makeText(this, R.string.activity_create_closed_group_group_name_too_long_error, Toast.LENGTH_LONG).show()
}
val selectedMembers = this.selectedMembers
val selectedMembers = this.selectContactsAdapter.selectedMembers
if (selectedMembers.count() < 2) {
return Toast.makeText(this, R.string.activity_create_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
}
@@ -151,26 +147,18 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
}.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 openConversation(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()
CreateClosedGroupTask(WeakReference(this), null, name.toString(), recipients, setOf( admin ))
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
}
// endregion
// region Tasks
// region Group Creation Task (Legacy)
internal class CreateClosedGroupTask(
private val activity: WeakReference<CreateClosedGroupActivity>,
private val profilePicture: Bitmap?,
private val name: String?,
private val members: Set<Recipient>,
private val admins: Set<Recipient>
private val activity: WeakReference<CreateClosedGroupActivity>,
private val profilePicture: Bitmap?,
private val name: String?,
private val members: Set<Recipient>,
private val admins: Set<Recipient>
) : AsyncTask<Void, Void, Optional<GroupManager.GroupActionResult>>() {
override fun doInBackground(vararg params: Void?): Optional<GroupManager.GroupActionResult> {
@@ -182,7 +170,8 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
val activity = activity.get() ?: return super.onPostExecute(result)
if (result.isPresent && result.get().threadId > -1) {
if (!activity.isFinishing) {
activity.openConversation(result.get().threadId, result.get().groupRecipient)
openConversationActivity(activity, result.get().threadId, result.get().groupRecipient)
activity.finish()
}
} else {
super.onPostExecute(result)
@@ -190,5 +179,15 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), MemberC
}
}
}
// endregion
}
}
// endregion
// region Convenience
private fun openConversationActivity(context: Context, threadId: Long, recipient: Recipient) {
val intent = Intent(context, 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)
context.startActivity(intent)
}
// endregion

View File

@@ -5,8 +5,8 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentPagerAdapter
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
@@ -136,7 +136,7 @@ class EnterPublicKeyFragment : Fragment() {
private fun copyPublicKey() {
val clipboard = activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Session ID", hexEncodedPublicKey)
clipboard.primaryClip = clip
clipboard.setPrimaryClip(clip)
Toast.makeText(context!!, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
}

View File

@@ -0,0 +1,244 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager
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.Toast
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_edit_closed_group.*
import kotlinx.android.synthetic.main.activity_linked_devices.recyclerView
import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.groups.GroupManager
import org.thoughtcrime.securesms.loki.dialogs.ClosedGroupEditingOptionsBottomSheet
import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.GroupUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.loki.utilities.toHexString
import java.io.IOException
class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() {
private val originalMembers = HashSet<String>()
private val members = HashSet<String>()
private var hasNameChanged = false
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 {
EditClosedGroupMembersAdapter(this, GlideApp.with(this), this::onMemberClick)
}
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!!.title = resources.getString(R.string.activity_edit_closed_group_title)
groupID = intent.getStringExtra(Companion.groupIDKey)
originalName = DatabaseFactory.getGroupDatabase(this).getGroup(groupID).get().title
name = originalName
addMembersClosedGroupButton.setOnClickListener { onAddMembersClick() }
recyclerView.adapter = memberListAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
lblGroupNameDisplay.text = originalName
cntGroupNameDisplay.setOnClickListener { isEditingName = true }
btnCancelGroupNameEdit.setOnClickListener { isEditingName = false }
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(Companion.loaderID, null, object : LoaderManager.LoaderCallbacks<List<String>> {
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<List<String>> {
return EditClosedGroupLoader(this@EditClosedGroupActivity, groupID)
}
override fun onLoadFinished(loader: Loader<List<String>>, members: List<String>) {
// We no longer need any subsequent loading events
// (they will occur on every activity resume).
LoaderManager.getInstance(this@EditClosedGroupActivity).destroyLoader(Companion.loaderID)
originalMembers.clear()
originalMembers.addAll(members.toHashSet())
updateMembers(originalMembers)
}
override fun onLoaderReset(loader: Loader<List<String>>) {
updateMembers(setOf())
}
})
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_apply, menu)
return members.isNotEmpty()
}
// endregion
// region Updating
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
Companion.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()
val changedMembers = members + selectedContacts
updateMembers(changedMembers)
}
}
}
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(members: Set<String>) {
this.members.clear()
this.members.addAll(members)
memberListAdapter.setMembers(members)
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
memberListAdapter.setLockedMembers(arrayListOf(userPublicKey))
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 {
when(item.itemId) {
R.id.applyButton -> commitChanges()
}
return super.onOptionsItemSelected(item)
}
private fun onMemberClick(member: String) {
val bottomSheet = ClosedGroupEditingOptionsBottomSheet()
bottomSheet.onRemoveTapped = {
val changedMembers = members - member
updateMembers(changedMembers)
bottomSheet.dismiss()
}
bottomSheet.show(supportFragmentManager, "GroupEditingOptionsBottomSheet")
}
private fun onAddMembersClick() {
val intent = Intent(this@EditClosedGroupActivity, SelectContactsActivity::class.java)
intent.putExtra(SelectContactsActivity.Companion.usersToExcludeKey, members.toTypedArray())
startActivityForResult(intent, Companion.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 = members != originalMembers
if (!hasNameChanged && !hasMemberListChanges) {
return finish()
}
val name = if (hasNameChanged) this.name else originalName
val members = this.members.map {
Recipient.from(this, Address.fromSerialized(it), false)
}.toSet()
val admins = members.toSet() //TODO For now, consider all the users to be admins.
var isSSKBasedClosedGroup: Boolean
var groupPublicKey: String?
try {
groupPublicKey = ClosedGroupsProtocol.doubleDecodeGroupID(groupID).toHexString()
isSSKBasedClosedGroup = DatabaseFactory.getSSKDatabase(this).isSSKBasedClosedGroup(groupPublicKey)
} catch (e: IOException) {
groupPublicKey = null
isSSKBasedClosedGroup = false
}
if (members.size < 2) {
return Toast.makeText(this, R.string.activity_edit_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show()
}
val maxGroupMembers = if (isSSKBasedClosedGroup) ClosedGroupsProtocol.groupSizeLimit else Companion.legacyGroupSizeLimit
if (members.size > maxGroupMembers) {
// TODO: Update copy for SSK based closed groups
return Toast.makeText(this, R.string.activity_edit_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show()
}
if (isSSKBasedClosedGroup) {
ClosedGroupsProtocol.update(this, groupPublicKey!!, members.map { it.address.serialize() },
name, admins.map { it.address.serialize() })
} else {
GroupManager.updateGroup(this, groupID, members, null, name, admins)
}
finish()
}
}

View File

@@ -0,0 +1,16 @@
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, val groupID: String) : AsyncLoader<List<String>>(context) {
override fun loadInBackground(): List<String> {
val members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupID, true)
return members.map {
it.address.toPhoneString()
}
}
}

View File

@@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.loki.views.UserView
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient
class EditClosedGroupMembersAdapter(
private val context: Context,
private val glide: GlideRequests,
private val memberClickListener: ((String) -> Unit)? = null
) : RecyclerView.Adapter<EditClosedGroupMembersAdapter.ViewHolder>() {
private val members = ArrayList<String>()
private val lockedMembers = HashSet<String>()
fun setMembers(members: Collection<String>) {
this.members.clear()
this.members.addAll(members)
notifyDataSetChanged()
}
fun setLockedMembers(members: Collection<String>) {
this.lockedMembers.clear()
this.lockedMembers.addAll(members)
notifyDataSetChanged()
}
override fun getItemCount(): Int = 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]
val lockedMember = lockedMembers.contains(member)
viewHolder.view.bind(Recipient.from(
context,
Address.fromSerialized(member), false),
glide,
if (lockedMember) UserView.ActionIndicator.None else UserView.ActionIndicator.Menu)
if (!lockedMember) {
viewHolder.view.setOnClickListener { this.memberClickListener?.invoke(member) }
}
}
class ViewHolder(val view: UserView) : RecyclerView.ViewHolder(view)
}

View File

@@ -1,7 +1,7 @@
package org.thoughtcrime.securesms.loki.activities
import android.app.AlertDialog
import android.arch.lifecycle.Observer
import androidx.lifecycle.Observer
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -11,10 +11,10 @@ import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle
import android.os.Handler
import android.support.v4.app.LoaderManager
import android.support.v4.content.Loader
import android.support.v4.content.LocalBroadcastManager
import android.support.v7.widget.LinearLayoutManager
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.LinearLayoutManager
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
@@ -121,7 +121,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
recyclerView.adapter = homeAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
// Set up empty state view
createNewPrivateChatButton.setOnClickListener { createNewPrivateChat() }
btnCreateNewPrivateChat.setOnClickListener { createNewPrivateChat() }
// This is a workaround for the fact that CursorRecyclerViewAdapter doesn't actually auto-update (even though it says it will)
LoaderManager.getInstance(this).restartLoader(0, null, object : LoaderManager.LoaderCallbacks<Cursor> {
@@ -223,7 +223,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == CreateClosedGroupActivity.createNewPrivateChatResultCode) {
if (resultCode == CreateClosedGroupActivity.closedGroupCreatedResultCode) {
createNewPrivateChat()
}
}

View File

@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.loki.activities
import android.content.Context
import android.database.Cursor
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter
import org.thoughtcrime.securesms.database.DatabaseFactory

View File

@@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.loki.activities
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentPagerAdapter
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import android.util.Patterns
import android.view.LayoutInflater
import android.view.View

View File

@@ -2,8 +2,8 @@ package org.thoughtcrime.securesms.loki.activities
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentPagerAdapter
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

View File

@@ -1,10 +1,10 @@
package org.thoughtcrime.securesms.loki.activities
import android.os.Bundle
import android.support.v4.app.LoaderManager
import android.support.v4.content.Loader
import android.support.v7.app.AlertDialog
import android.support.v7.widget.LinearLayoutManager
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import android.view.View

View File

@@ -1,7 +1,7 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Context
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup
import org.thoughtcrime.securesms.devicelist.Device
import org.thoughtcrime.securesms.loki.views.DeviceView

View File

@@ -5,7 +5,7 @@ import android.content.Intent
import android.graphics.drawable.TransitionDrawable
import android.net.Uri
import android.os.Bundle
import android.support.annotation.DrawableRes
import androidx.annotation.DrawableRes
import android.view.Menu
import android.view.MenuItem
import android.view.View

View File

@@ -7,7 +7,7 @@ import android.content.IntentFilter
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.support.v4.content.LocalBroadcastManager
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import android.util.AttributeSet
import android.util.TypedValue
import android.view.Gravity

View File

@@ -5,8 +5,8 @@ import android.content.Intent
import android.graphics.Bitmap
import android.os.Bundle
import android.os.Environment
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentPagerAdapter
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentPagerAdapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

View File

@@ -153,7 +153,7 @@ class RegisterActivity : BaseActionBarActivity() {
private fun copyPublicKey() {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Session ID", keyPair!!.hexEncodedPublicKey)
clipboard.primaryClip = clip
clipboard.setPrimaryClip(clip)
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
}

View File

@@ -78,7 +78,7 @@ class SeedActivity : BaseActionBarActivity() {
revealSeed()
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Seed", seed)
clipboard.primaryClip = clip
clipboard.setPrimaryClip(clip)
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
}
// endregion

View File

@@ -0,0 +1,91 @@
package org.thoughtcrime.securesms.loki.activities
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import androidx.recyclerview.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import android.view.View
import kotlinx.android.synthetic.main.activity_create_closed_group.*
import kotlinx.android.synthetic.main.activity_linked_devices.recyclerView
import network.loki.messenger.R
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.mms.GlideApp
class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderManager.LoaderCallbacks<List<String>> {
private var members = listOf<String>()
set(value) { field = value; selectContactsAdapter.members = value }
private lateinit var usersToExclude: Set<String>
private val selectContactsAdapter by lazy {
SelectContactsAdapter(this, GlideApp.with(this))
}
companion object {
val usersToExcludeKey = "usersToExcludeKey"
val selectedContactsKey = "selectedContactsKey"
}
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
setContentView(R.layout.activity_select_contacts)
supportActionBar!!.title = resources.getString(R.string.activity_select_contacts_title)
usersToExclude = intent.getStringArrayExtra(Companion.usersToExcludeKey)?.toSet() ?: setOf()
recyclerView.adapter = selectContactsAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
LoaderManager.getInstance(this).initLoader(0, null, this)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_done, menu)
return members.isNotEmpty()
}
// endregion
// region Updating
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<List<String>> {
return SelectContactsLoader(this, usersToExclude)
}
override fun onLoadFinished(loader: Loader<List<String>>, members: List<String>) {
update(members)
}
override fun onLoaderReset(loader: Loader<List<String>>) {
update(listOf())
}
private fun update(members: List<String>) {
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 {
when(item.itemId) {
R.id.doneButton -> closeAndReturnSelected()
}
return super.onOptionsItemSelected(item)
}
private fun closeAndReturnSelected() {
val selectedMembers = selectContactsAdapter.selectedMembers
val selectedContacts = selectedMembers.toTypedArray()
val intent = Intent()
intent.putExtra(selectedContactsKey, selectedContacts)
setResult(Activity.RESULT_OK, intent)
finish()
}
// endregion
}

View File

@@ -1,19 +1,17 @@
package org.thoughtcrime.securesms.loki.activities
import android.content.Context
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup
import org.thoughtcrime.securesms.database.Address
import org.thoughtcrime.securesms.loki.views.UserView
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient
class CreateClosedGroupAdapter(private val context: Context) : RecyclerView.Adapter<CreateClosedGroupAdapter.ViewHolder>() {
lateinit var glide: GlideRequests
class SelectContactsAdapter(private val context: Context, private val glide: GlideRequests) : RecyclerView.Adapter<SelectContactsAdapter.ViewHolder>() {
val selectedMembers = mutableSetOf<String>()
var members = listOf<String>()
set(value) { field = value; notifyDataSetChanged() }
var memberClickListener: MemberClickListener? = null
class ViewHolder(val view: UserView) : RecyclerView.ViewHolder(view)
@@ -28,12 +26,17 @@ class CreateClosedGroupAdapter(private val context: Context) : RecyclerView.Adap
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val member = members[position]
viewHolder.view.setOnClickListener { memberClickListener?.onMemberClick(member) }
viewHolder.view.setOnClickListener { onMemberClick(member) }
val isSelected = selectedMembers.contains(member)
viewHolder.view.bind(Recipient.from(context, Address.fromSerialized(member), false), isSelected, glide)
viewHolder.view.bind(Recipient.from(
context,
Address.fromSerialized(member), false),
glide,
UserView.ActionIndicator.Tick,
isSelected)
}
fun onMemberClick(member: String) {
private fun onMemberClick(member: String) {
if (selectedMembers.contains(member)) {
selectedMembers.remove(member)
} else {
@@ -42,9 +45,4 @@ class CreateClosedGroupAdapter(private val context: Context) : RecyclerView.Adap
val index = members.indexOf(member)
notifyItemChanged(index)
}
}
interface MemberClickListener {
fun onMemberClick(member: String)
}

View File

@@ -4,12 +4,12 @@ import android.content.Context
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
import org.thoughtcrime.securesms.util.AsyncLoader
class CreateClosedGroupLoader(context: Context) : AsyncLoader<List<String>>(context) {
class SelectContactsLoader(context: Context, val usersToExclude: Set<String>) : AsyncLoader<List<String>>(context) {
override fun loadInBackground(): List<String> {
val contacts = ContactUtilities.getAllContacts(context)
return contacts.filter { contact ->
!contact.recipient.isGroupRecipient && !contact.isOurDevice && !contact.isSlave
!contact.recipient.isGroupRecipient && !contact.isOurDevice && !contact.isSlave && !usersToExclude.contains(contact.recipient.address.toPhoneString())
}.map {
it.recipient.address.toPhoneString()
}

View File

@@ -84,8 +84,8 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
profilePictureView.isLarge = true
profilePictureView.update()
profilePictureView.setOnClickListener { showEditProfilePictureUI() }
displayNameContainer.setOnClickListener { showEditDisplayNameUI() }
displayNameTextView.text = DatabaseFactory.getLokiUserDatabase(this).getDisplayName(hexEncodedPublicKey)
ctnGroupNameSection.setOnClickListener { showEditDisplayNameUI() }
btnGroupNameDisplay.text = DatabaseFactory.getLokiUserDatabase(this).getDisplayName(hexEncodedPublicKey)
publicKeyTextView.text = hexEncodedPublicKey
copyButton.setOnClickListener { copyPublicKey() }
shareButton.setOnClickListener { sharePublicKey() }
@@ -154,7 +154,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
cancelButton.visibility = if (isEditingDisplayName) View.VISIBLE else View.GONE
showQRCodeButton.visibility = if (isEditingDisplayName) View.GONE else View.VISIBLE
saveButton.visibility = if (isEditingDisplayName) View.VISIBLE else View.GONE
displayNameTextView.visibility = if (isEditingDisplayName) View.INVISIBLE else View.VISIBLE
btnGroupNameDisplay.visibility = if (isEditingDisplayName) View.INVISIBLE else View.VISIBLE
displayNameEditText.visibility = if (isEditingDisplayName) View.VISIBLE else View.INVISIBLE
val titleTextViewLayoutParams = titleTextView.layoutParams as LinearLayout.LayoutParams
titleTextViewLayoutParams.leftMargin = if (isEditingDisplayName) toPx(16, resources) else 0
@@ -198,7 +198,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
}
all(promises).alwaysUi {
if (displayName != null) {
displayNameTextView.text = displayName
btnGroupNameDisplay.text = displayName
}
displayNameToBeUploaded = null
if (isUpdatingProfilePicture && profilePicture != null) {
@@ -248,7 +248,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
private fun copyPublicKey() {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Session ID", hexEncodedPublicKey)
clipboard.primaryClip = clip
clipboard.setPrimaryClip(clip)
Toast.makeText(this, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
}