session-android/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt

320 lines
16 KiB
Kotlin
Raw Normal View History

2019-12-17 14:27:59 +01:00
package org.thoughtcrime.securesms.loki.redesign.activities
2019-12-19 16:10:11 +01:00
import android.annotation.SuppressLint
2020-02-19 05:34:02 +01:00
import android.app.AlertDialog
2019-12-19 11:15:58 +01:00
import android.arch.lifecycle.Observer
2019-12-17 15:15:13 +01:00
import android.content.Intent
2019-12-19 11:49:23 +01:00
import android.database.Cursor
2019-12-19 16:10:11 +01:00
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Paint
2020-01-07 06:44:53 +01:00
import android.os.AsyncTask
2019-12-17 14:27:59 +01:00
import android.os.Bundle
import android.os.Handler
2019-12-19 11:49:23 +01:00
import android.support.v4.app.LoaderManager
import android.support.v4.content.Loader
2019-12-17 14:27:59 +01:00
import android.support.v7.widget.LinearLayoutManager
2019-12-19 16:10:11 +01:00
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.view.View
2020-02-19 05:34:02 +01:00
import android.widget.Toast
2019-12-17 14:27:59 +01:00
import kotlinx.android.synthetic.main.activity_home.*
import network.loki.messenger.R
import org.thoughtcrime.securesms.ApplicationContext
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
2019-12-17 15:15:13 +01:00
import org.thoughtcrime.securesms.conversation.ConversationActivity
2019-12-17 14:27:59 +01:00
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.ThreadDatabase
2019-12-17 15:15:13 +01:00
import org.thoughtcrime.securesms.database.model.ThreadRecord
2019-12-19 16:10:11 +01:00
import org.thoughtcrime.securesms.loki.getColorWithID
2019-12-17 15:15:13 +01:00
import org.thoughtcrime.securesms.loki.redesign.utilities.push
import org.thoughtcrime.securesms.loki.redesign.utilities.show
2019-12-17 15:15:13 +01:00
import org.thoughtcrime.securesms.loki.redesign.views.ConversationView
import org.thoughtcrime.securesms.loki.redesign.views.SeedReminderViewDelegate
2019-12-19 11:15:58 +01:00
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests
2020-01-07 06:44:53 +01:00
import org.thoughtcrime.securesms.notifications.MessageNotifier
2020-02-19 05:34:02 +01:00
import org.thoughtcrime.securesms.util.GroupUtil
2019-12-17 14:27:59 +01:00
import org.thoughtcrime.securesms.util.TextSecurePreferences
2019-12-19 16:10:11 +01:00
import kotlin.math.abs
2019-12-17 14:27:59 +01:00
class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListener, SeedReminderViewDelegate {
2019-12-19 11:15:58 +01:00
private lateinit var glide: GlideRequests
2019-12-17 14:27:59 +01:00
2020-01-06 04:26:52 +01:00
private val hexEncodedPublicKey: String
get() {
val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(this)
val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(this)
return masterHexEncodedPublicKey ?: userHexEncodedPublicKey
}
// region Lifecycle
2019-12-17 14:27:59 +01:00
constructor() : super()
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)
// Process any outstanding deletes
val threadDatabase = DatabaseFactory.getThreadDatabase(this)
val archivedConversationCount = threadDatabase.archivedConversationListCount
if (archivedConversationCount > 0) {
val archivedConversations = threadDatabase.archivedConversationList
archivedConversations.moveToFirst()
fun deleteThreadAtCurrentPosition() {
val threadID = archivedConversations.getLong(archivedConversations.getColumnIndex(ThreadDatabase.ID))
AsyncTask.execute {
threadDatabase.deleteConversation(threadID)
MessageNotifier.updateNotification(this)
}
}
deleteThreadAtCurrentPosition()
while (archivedConversations.moveToNext()) {
deleteThreadAtCurrentPosition()
}
}
2020-02-04 04:20:42 +01:00
// Double check that the long poller is up
(applicationContext as ApplicationContext).startLongPollingIfNeeded()
2019-12-17 14:27:59 +01:00
// Set content view
setContentView(R.layout.activity_home)
2020-01-07 02:00:30 +01:00
// Set custom toolbar
2020-01-06 02:07:55 +01:00
setSupportActionBar(toolbar)
2019-12-19 11:15:58 +01:00
// Set up Glide
glide = GlideApp.with(this)
2020-01-06 02:07:55 +01:00
// Set up toolbar buttons
profileButton.glide = glide
2020-01-06 04:26:52 +01:00
profileButton.hexEncodedPublicKey = hexEncodedPublicKey
2020-01-06 02:07:55 +01:00
profileButton.update()
profileButton.setOnClickListener { openSettings() }
2020-01-31 03:57:24 +01:00
createClosedGroupButton.setOnClickListener { createClosedGroup() }
2020-01-06 02:07:55 +01:00
joinPublicChatButton.setOnClickListener { joinPublicChat() }
// Set up seed reminder view
val isMasterDevice = (TextSecurePreferences.getMasterHexEncodedPublicKey(this) == null)
val hasViewedSeed = TextSecurePreferences.getHasViewedSeed(this)
if (!hasViewedSeed && isMasterDevice) {
val seedReminderViewTitle = SpannableString("You're almost finished! 80%")
seedReminderViewTitle.setSpan(ForegroundColorSpan(resources.getColorWithID(R.color.accent, theme)), 24, 27, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
seedReminderView.title = seedReminderViewTitle
seedReminderView.subtitle = "Secure your account by saving your recovery phrase"
seedReminderView.setProgress(80, false)
seedReminderView.delegate = this
} else {
seedReminderView.visibility = View.GONE
}
2019-12-17 14:27:59 +01:00
// Set up recycler view
val cursor = DatabaseFactory.getThreadDatabase(this).conversationList
2019-12-19 11:15:58 +01:00
val homeAdapter = HomeAdapter(this, cursor)
homeAdapter.glide = glide
homeAdapter.conversationClickListener = this
recyclerView.adapter = homeAdapter
2019-12-17 14:27:59 +01:00
recyclerView.layoutManager = LinearLayoutManager(this)
2019-12-19 16:10:11 +01:00
ItemTouchHelper(SwipeCallback(this)).attachToRecyclerView(recyclerView)
2019-12-19 11:49:23 +01:00
// 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> {
override fun onCreateLoader(id: Int, bundle: Bundle?): Loader<Cursor> {
return HomeLoader(this@HomeActivity)
}
override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor?) {
homeAdapter.changeCursor(cursor)
}
override fun onLoaderReset(cursor: Loader<Cursor>) {
homeAdapter.changeCursor(null)
}
})
2019-12-17 15:15:13 +01:00
// Set up new conversation button
newConversationButton.setOnClickListener { createPrivateChat() }
2019-12-19 11:15:58 +01:00
// Set up typing observer
2019-12-19 11:49:23 +01:00
ApplicationContext.getInstance(this).typingStatusRepository.typingThreads.observe(this, Observer<Set<Long>> { threadIDs ->
val adapter = recyclerView.adapter as HomeAdapter
adapter.typingThreadIDs = threadIDs ?: setOf()
2019-12-19 11:15:58 +01:00
})
2019-12-17 14:27:59 +01:00
// Set up public chats and RSS feeds if needed
if (TextSecurePreferences.getLocalNumber(this) != null) {
val application = ApplicationContext.getInstance(this)
application.createDefaultPublicChatsIfNeeded()
application.createRSSFeedsIfNeeded()
application.lokiPublicChatManager.startPollersIfNeeded()
application.startRSSFeedPollersIfNeeded()
}
}
override fun onResume() {
super.onResume()
val isMasterDevice = (TextSecurePreferences.getMasterHexEncodedPublicKey(this) == null)
val hasViewedSeed = TextSecurePreferences.getHasViewedSeed(this)
if (hasViewedSeed || !isMasterDevice) {
seedReminderView.visibility = View.GONE
}
// if (!TextSecurePreferences.getHasSeenOpenGroupSuggestionSheet(this)) {
// val bottomSheet = OpenGroupSuggestionBottomSheet()
// bottomSheet.onJoinTapped = {
// TextSecurePreferences.setHasSeenOpenGroupSuggestionSheet(this)
// bottomSheet.dismiss()
// // TODO: Duplication of the code in JoinPublicChatActivity
// val application = ApplicationContext.getInstance(this)
// val channel: Long = 1
// val displayName = TextSecurePreferences.getProfileName(this)
// val lokiPublicChatAPI = application.lokiPublicChatAPI!!
// val url = "https://chat.getsession.org"
// application.lokiPublicChatManager.addChat(url, channel).successUi {
// lokiPublicChatAPI.getMessages(channel, url)
// lokiPublicChatAPI.setDisplayName(displayName, url)
// lokiPublicChatAPI.join(channel, url)
// val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(this)
// val profileUrl: String? = TextSecurePreferences.getProfileAvatarUrl(this)
// lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl)
// }
// }
// bottomSheet.onDismissTapped = {
// TextSecurePreferences.setHasSeenOpenGroupSuggestionSheet(this)
// bottomSheet.dismiss()
// }
// bottomSheet.show(supportFragmentManager, bottomSheet.tag)
// }
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == CreateClosedGroupActivity.createNewPrivateChatResultCode) {
createPrivateChat()
}
}
// endregion
override fun handleSeedReminderViewContinueButtonTapped() {
val intent = Intent(this, SeedActivity::class.java)
show(intent)
}
2019-12-17 15:15:13 +01:00
override fun onConversationClick(view: ConversationView) {
val thread = view.thread ?: return
openConversation(thread)
}
override fun onLongConversationClick(view: ConversationView) {
// Do nothing
2019-12-17 15:15:13 +01:00
}
private fun openConversation(thread: ThreadRecord) {
val intent = Intent(this, ConversationActivity::class.java)
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, thread.recipient.address)
2019-12-17 15:15:13 +01:00
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, thread.threadId)
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, thread.distributionType)
intent.putExtra(ConversationActivity.TIMING_EXTRA, System.currentTimeMillis())
intent.putExtra(ConversationActivity.LAST_SEEN_EXTRA, thread.lastSeen)
intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1)
push(intent)
}
2020-01-06 02:07:55 +01:00
private fun openSettings() {
2020-01-06 04:26:52 +01:00
val intent = Intent(this, SettingsActivity::class.java)
show(intent)
2020-01-06 02:07:55 +01:00
}
2019-12-17 15:15:13 +01:00
private fun createPrivateChat() {
val intent = Intent(this, CreatePrivateChatActivity::class.java)
show(intent)
2019-12-17 15:15:13 +01:00
}
2020-01-31 03:57:24 +01:00
private fun createClosedGroup() {
val intent = Intent(this, CreateClosedGroupActivity::class.java)
show(intent, true)
2020-01-31 03:57:24 +01:00
}
private fun joinPublicChat() {
val intent = Intent(this, JoinPublicChatActivity::class.java)
show(intent)
}
2019-12-19 16:10:11 +01:00
private class SwipeCallback(val activity: HomeActivity) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
2019-12-19 16:10:11 +01:00
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
return false
}
@SuppressLint("StaticFieldLeak")
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
2020-02-21 05:35:53 +01:00
viewHolder as HomeAdapter.ViewHolder
val threadID = viewHolder.view.thread!!.threadId
val recipient = viewHolder.view.thread!!.recipient
val threadDatabase = DatabaseFactory.getThreadDatabase(activity)
val deleteThread = object : Runnable {
override fun run() {
AsyncTask.execute {
2020-01-22 00:46:04 +01:00
val publicChat = DatabaseFactory.getLokiThreadDatabase(activity).getPublicChat(threadID)
if (publicChat != null) {
2020-01-23 23:59:37 +01:00
val apiDatabase = DatabaseFactory.getLokiAPIDatabase(activity)
apiDatabase.removeLastMessageServerID(publicChat.channel, publicChat.server)
apiDatabase.removeLastDeletionServerID(publicChat.channel, publicChat.server)
2020-01-22 00:46:04 +01:00
ApplicationContext.getInstance(activity).lokiPublicChatAPI!!.leave(publicChat.channel, publicChat.server)
}
2020-01-23 23:59:37 +01:00
threadDatabase.deleteConversation(threadID)
MessageNotifier.updateNotification(activity)
}
2020-01-07 06:44:53 +01:00
}
}
2020-02-21 05:35:53 +01:00
val dialogMessage = if (recipient.isGroupRecipient) R.string.activity_home_leave_group_dialog_message else R.string.activity_home_delete_conversation_dialog_message
val dialog = AlertDialog.Builder(activity)
dialog.setMessage(dialogMessage)
dialog.setPositiveButton(R.string.yes) { _, _ ->
val isClosedGroup = recipient.address.isSignalGroup
2020-02-21 05:35:53 +01:00
// Send a leave group message if this is an active closed group
if (isClosedGroup && DatabaseFactory.getGroupDatabase(activity).isActive(recipient.address.toGroupString())) {
2020-02-19 05:34:02 +01:00
if (!GroupUtil.leaveGroup(activity, recipient)) {
2020-02-21 05:35:53 +01:00
Toast.makeText(activity, "Couldn't leave group", Toast.LENGTH_LONG).show()
2020-02-19 05:34:02 +01:00
clearView(activity.recyclerView, viewHolder)
return@setPositiveButton
}
}
2020-02-21 05:35:53 +01:00
// Archive the conversation and then delete it after 10 seconds (the case where the
// app was closed before the conversation could be deleted is handled in onCreate)
2020-02-19 05:34:02 +01:00
threadDatabase.archiveConversation(threadID)
val delay = if (isClosedGroup) 10000L else 1000L
2020-02-19 05:34:02 +01:00
val handler = Handler()
handler.postDelayed(deleteThread, delay)
2020-02-19 05:34:02 +01:00
// Notify the user
val toastMessage = if (recipient.isGroupRecipient) R.string.MessageRecord_left_group else R.string.activity_home_conversation_deleted_message
2020-02-21 05:35:53 +01:00
Toast.makeText(activity, toastMessage, Toast.LENGTH_LONG).show()
2020-02-19 05:34:02 +01:00
}
2020-02-21 05:35:53 +01:00
dialog.setNegativeButton(R.string.no) { _, _ ->
2020-02-19 05:34:02 +01:00
clearView(activity.recyclerView, viewHolder)
2020-01-07 06:44:53 +01:00
}
2020-02-21 05:35:53 +01:00
dialog.create().show()
2019-12-19 16:10:11 +01:00
}
override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dx: Float, dy: Float, actionState: Int, isCurrentlyActive: Boolean) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && dx < 0) {
2019-12-19 16:10:11 +01:00
val itemView = viewHolder.itemView
animate(viewHolder, dx)
val backgroundPaint = Paint()
backgroundPaint.color = activity.resources.getColorWithID(R.color.destructive, activity.theme)
c.drawRect(itemView.right.toFloat() - abs(dx), itemView.top.toFloat(), itemView.right.toFloat(), itemView.bottom.toFloat(), backgroundPaint)
val icon = BitmapFactory.decodeResource(activity.resources, R.drawable.ic_trash_filled_32)
val iconPaint = Paint()
val left = itemView.right.toFloat() - abs(dx) + activity.resources.getDimension(R.dimen.medium_spacing)
val top = itemView.top.toFloat() + (itemView.bottom.toFloat() - itemView.top.toFloat() - icon.height) / 2
c.drawBitmap(icon, left, top, iconPaint)
} else {
super.onChildDraw(c, recyclerView, viewHolder, dx, dy, actionState, isCurrentlyActive)
2019-12-19 16:10:11 +01:00
}
}
2020-01-07 06:44:53 +01:00
private fun animate(viewHolder: RecyclerView.ViewHolder, dx: Float) {
2020-01-07 06:44:53 +01:00
val alpha = 1.0f - abs(dx) / viewHolder.itemView.width.toFloat()
viewHolder.itemView.alpha = alpha
viewHolder.itemView.translationX = dx
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
viewHolder.itemView.alpha = 1.0f
viewHolder.itemView.translationX = 0.0f
}
2019-12-19 16:10:11 +01:00
}
// endregion
2019-12-17 14:27:59 +01:00
}