From 7da4f1f6ae4d0d32ac88460610f6b46d016a89bd Mon Sep 17 00:00:00 2001
From: Niels Andriesse <andriesseniels@gmail.com>
Date: Wed, 8 Jan 2020 10:50:11 +1100
Subject: [PATCH] Fix conversation deletion & public chat joining

---
 res/layout/activity_home.xml                  |  1 +
 res/layout/activity_join_public_chat.xml      | 41 ++++++--
 res/values/dimens.xml                         |  2 +-
 res/values/styles.xml                         | 13 +++
 res/values/themes.xml                         |  2 +
 .../loki/redesign/activities/HomeActivity.kt  | 95 +++++++++++--------
 .../activities/JoinPublicChatActivity.kt      | 52 +++++++++-
 7 files changed, 155 insertions(+), 51 deletions(-)

diff --git a/res/layout/activity_home.xml b/res/layout/activity_home.xml
index 498738cf9d..b6239f1348 100644
--- a/res/layout/activity_home.xml
+++ b/res/layout/activity_home.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/contentView"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical"
diff --git a/res/layout/activity_join_public_chat.xml b/res/layout/activity_join_public_chat.xml
index d6bf8f66d7..3ad2c9465d 100644
--- a/res/layout/activity_join_public_chat.xml
+++ b/res/layout/activity_join_public_chat.xml
@@ -1,14 +1,39 @@
 <?xml version="1.0" encoding="utf-8"?>
-<android.support.v4.view.ViewPager
+<RelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/viewPager"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
+    android:layout_height="match_parent">
 
-    <android.support.design.widget.TabLayout
-        style="@style/Session.DarkTabLayout"
-        android:id="@+id/tabLayout"
+    <android.support.v4.view.ViewPager
+        android:id="@+id/viewPager"
         android:layout_width="match_parent"
-        android:layout_height="@dimen/tab_bar_height" />
+        android:layout_height="match_parent" >
 
-</android.support.v4.view.ViewPager>
\ No newline at end of file
+        <android.support.design.widget.TabLayout
+            style="@style/Session.DarkTabLayout"
+            android:id="@+id/tabLayout"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/tab_bar_height" />
+
+    </android.support.v4.view.ViewPager>
+
+    <RelativeLayout
+        android:id="@+id/loader"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#A4000000"
+        android:visibility="gone"
+        android:alpha="0">
+
+        <com.github.ybq.android.spinkit.SpinKitView
+            style="@style/SpinKitView.Large.ThreeBounce"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:layout_centerInParent="true"
+            app:SpinKit_Color="@color/text" />
+
+    </RelativeLayout>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 83960a07fa..cc41589e8a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -37,7 +37,7 @@
     <dimen name="large_spacing">24dp</dimen>
     <dimen name="very_large_spacing">35dp</dimen>
     <dimen name="massive_spacing">64dp</dimen>
-    <dimen name="new_conversation_button_bottom_offset">40dp</dimen>
+    <dimen name="new_conversation_button_bottom_offset">56dp</dimen>
     <dimen name="onboarding_button_bottom_offset">40dp</dimen>
 
     <!-- Session -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8af14e202a..88a1725dff 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -16,6 +16,19 @@
         <item name="android:textSize">@dimen/very_large_font_size</item>
     </style>
 
+    <style name="Session.AlertDialog" parent="ThemeOverlay.AppCompat.Dialog.Alert">
+        <item name="buttonBarNegativeButtonStyle">@style/Session.AlertDialog.NegativeButtonStyle</item>
+        <item name="buttonBarPositiveButtonStyle">@style/Session.AlertDialog.PositiveButtonStyle</item>
+    </style>
+
+    <style name="Session.AlertDialog.NegativeButtonStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
+        <item name="android:textColor">@color/accent</item>
+    </style>
+
+    <style name="Session.AlertDialog.PositiveButtonStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
+        <item name="android:textColor">@color/accent</item>
+    </style>
+
     <style name="Session.DarkTabLayout" parent="Widget.Design.TabLayout">
         <item name="tabIndicatorColor">@color/accent</item>
         <item name="tabIndicatorHeight">@dimen/accent_line_thickness</item>
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 175217d7a3..bad8e588e7 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -8,6 +8,7 @@
         <item name="colorPrimary">@color/action_bar_background</item>
         <item name="colorPrimaryDark">@color/action_bar_background</item>
         <item name="android:navigationBarColor">@color/navigation_bar_background</item>
+        <item name="alertDialogTheme">@style/Session.AlertDialog</item>
     </style>
 
     <style name="Session.DarkTheme.NoActionBar" parent="@style/Theme.AppCompat.NoActionBar">
@@ -15,6 +16,7 @@
         <item name="colorPrimary">@color/action_bar_background</item>
         <item name="colorPrimaryDark">@color/action_bar_background</item>
         <item name="android:navigationBarColor">@color/navigation_bar_background</item>
+        <item name="alertDialogTheme">@style/Session.AlertDialog</item>
     </style>
     <!-- Session -->
 
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt
index 3ae25ca036..a2d292b43a 100644
--- a/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/HomeActivity.kt
@@ -1,9 +1,7 @@
 package org.thoughtcrime.securesms.loki.redesign.activities
 
-import android.animation.ValueAnimator
 import android.annotation.SuppressLint
 import android.arch.lifecycle.Observer
-import android.content.Context
 import android.content.Intent
 import android.database.Cursor
 import android.graphics.BitmapFactory
@@ -11,9 +9,10 @@ import android.graphics.Canvas
 import android.graphics.Paint
 import android.os.AsyncTask
 import android.os.Bundle
+import android.os.Handler
+import android.support.design.widget.Snackbar
 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 android.support.v7.widget.RecyclerView
 import android.support.v7.widget.helper.ItemTouchHelper
@@ -23,6 +22,7 @@ import org.thoughtcrime.securesms.ApplicationContext
 import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
 import org.thoughtcrime.securesms.conversation.ConversationActivity
 import org.thoughtcrime.securesms.database.DatabaseFactory
+import org.thoughtcrime.securesms.database.ThreadDatabase
 import org.thoughtcrime.securesms.database.model.ThreadRecord
 import org.thoughtcrime.securesms.loki.getColorWithID
 import org.thoughtcrime.securesms.loki.redesign.utilities.push
@@ -48,6 +48,24 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
 
     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()
+            }
+        }
         // Set content view
         setContentView(R.layout.activity_home)
         // Set custom toolbar
@@ -106,7 +124,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
     }
 
     override fun onLongConversationClick(view: ConversationView) {
-        // TODO: Implement
+        // Do nothing
     }
 
     private fun openConversation(thread: ThreadRecord) {
@@ -135,7 +153,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
         startActivity(intent)
     }
 
-    private class SwipeCallback(val context: Context) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
+    private class SwipeCallback(val activity: HomeActivity) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
 
         override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
             return false
@@ -143,51 +161,48 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
 
         @SuppressLint("StaticFieldLeak")
         override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
-            val builder = AlertDialog.Builder(context)
-            builder.setIconAttribute(R.attr.dialog_alert_icon)
-            builder.setTitle("Delete Selected Conversation?")
-            builder.setMessage("This will permanently delete the selected conversation.")
-            builder.setCancelable(true)
-            builder.setPositiveButton("Delete") { dialog, _ ->
-                val threadID = (viewHolder as HomeAdapter.ViewHolder).view.thread!!.threadId
-                AsyncTask.execute {
-                    DatabaseFactory.getThreadDatabase(context).deleteConversation(threadID)
-                    MessageNotifier.updateNotification(context)
+            val threadID = (viewHolder as HomeAdapter.ViewHolder).view.thread!!.threadId
+            val threadDatabase = DatabaseFactory.getThreadDatabase(activity)
+            threadDatabase.archiveConversation(threadID)
+            val deleteThread = object : Runnable {
+
+                override fun run() {
+                    AsyncTask.execute {
+                        threadDatabase.deleteConversation(threadID)
+                        MessageNotifier.updateNotification(activity)
+                    }
                 }
-                dialog.dismiss()
             }
-            builder.setNegativeButton(android.R.string.cancel) { dialog, _ ->
-                val animator = ValueAnimator.ofFloat(viewHolder.itemView.translationX, 0.0f)
-                animator.duration = 150
-                animator.addUpdateListener {
-                    update(viewHolder, animator.animatedValue as Float)
-                }
-                animator.start()
-                dialog.dismiss()
+            val handler = Handler()
+            handler.postDelayed(deleteThread, 5000)
+            val snackbar = Snackbar.make(activity.contentView, "Conversation Deleted", Snackbar.LENGTH_LONG)
+            snackbar.setAction("Undo") {
+                threadDatabase.unarchiveConversation(threadID)
+                handler.removeCallbacks(deleteThread)
+                animate(viewHolder, 0.0f)
             }
-            builder.create().show()
+            snackbar.setActionTextColor(activity.resources.getColorWithID(R.color.accent, activity.theme))
+            snackbar.show()
         }
 
         override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dx: Float, dy: Float, actionState: Int, isCurrentlyActive: Boolean) {
-            if (actionState != ItemTouchHelper.ACTION_STATE_SWIPE) {
-                super.onChildDraw(c, recyclerView, viewHolder, dx, dy, actionState, isCurrentlyActive)
-            } else {
+            if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && dx < 0) {
                 val itemView = viewHolder.itemView
-                if (dx < 0) {
-                    val backgroundPaint = Paint()
-                    backgroundPaint.color = context.resources.getColorWithID(R.color.destructive, context.theme)
-                    c.drawRect(itemView.right.toFloat() - abs(dx), itemView.top.toFloat(), itemView.right.toFloat(), itemView.bottom.toFloat(), backgroundPaint)
-                    val icon = BitmapFactory.decodeResource(context.resources, R.drawable.ic_trash_filled_32)
-                    val iconPaint = Paint()
-                    val left = itemView.right.toFloat() - abs(dx) + context.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)
-                }
-                update(viewHolder, dx)
+                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)
             }
         }
 
-        private fun update(viewHolder: RecyclerView.ViewHolder, dx: Float) {
+        private fun animate(viewHolder: RecyclerView.ViewHolder, dx: Float) {
             val alpha = 1.0f - abs(dx) / viewHolder.itemView.width.toFloat()
             viewHolder.itemView.alpha = alpha
             viewHolder.itemView.translationX = dx
diff --git a/src/org/thoughtcrime/securesms/loki/redesign/activities/JoinPublicChatActivity.kt b/src/org/thoughtcrime/securesms/loki/redesign/activities/JoinPublicChatActivity.kt
index 771e5aae4b..b190e75bf7 100644
--- a/src/org/thoughtcrime/securesms/loki/redesign/activities/JoinPublicChatActivity.kt
+++ b/src/org/thoughtcrime/securesms/loki/redesign/activities/JoinPublicChatActivity.kt
@@ -1,17 +1,28 @@
 package org.thoughtcrime.securesms.loki.redesign.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 android.util.Patterns
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.view.inputmethod.InputMethodManager
+import android.widget.Toast
 import kotlinx.android.synthetic.main.activity_join_public_chat.*
 import kotlinx.android.synthetic.main.fragment_enter_chat_url.*
 import network.loki.messenger.R
+import nl.komponents.kovenant.ui.failUi
+import nl.komponents.kovenant.ui.successUi
+import org.thoughtcrime.securesms.ApplicationContext
+import org.thoughtcrime.securesms.BaseActionBarActivity
 import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
+import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
 import org.thoughtcrime.securesms.loki.redesign.fragments.ScanQRCodeWrapperFragment
 import org.thoughtcrime.securesms.loki.redesign.fragments.ScanQRCodeWrapperFragmentDelegate
+import org.thoughtcrime.securesms.util.TextSecurePreferences
 
 class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
     private val adapter = JoinPublicChatActivityAdapter(this)
@@ -29,13 +40,48 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
     }
     // endregion
 
+    // region Updating
+    private fun showLoader() {
+        loader.visibility = View.VISIBLE
+        loader.animate().setDuration(150).alpha(1.0f).start()
+    }
+
+    private fun hideLoader() {
+        loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() {
+
+            override fun onAnimationEnd(animation: Animator?) {
+                super.onAnimationEnd(animation)
+                loader.visibility = View.GONE
+            }
+        })
+    }
+    // endregion
+
     // region Interaction
     override fun handleQRCodeScanned(url: String) {
         joinPublicChatIfPossible(url)
     }
 
     fun joinPublicChatIfPossible(url: String) {
-        // TODO: Implement
+        if (!Patterns.WEB_URL.matcher(url).matches() || !url.startsWith("https://")) {
+            return Toast.makeText(this, "Invalid URL", Toast.LENGTH_SHORT).show()
+        }
+        showLoader()
+        val application = ApplicationContext.getInstance(this)
+        val channel: Long = 1
+        val displayName = TextSecurePreferences.getProfileName(this)
+        val lokiPublicChatAPI = application.lokiPublicChatAPI!!
+        application.lokiPublicChatManager.addChat(url, channel).successUi {
+            lokiPublicChatAPI.getMessages(channel, url)
+            lokiPublicChatAPI.setDisplayName(displayName, url)
+            val profileKey: ByteArray = ProfileKeyUtil.getProfileKey(this)
+            val profileUrl: String? = TextSecurePreferences.getProfileAvatarUrl(this)
+            lokiPublicChatAPI.setProfilePicture(url, profileKey, profileUrl)
+            finish()
+        }.failUi {
+            hideLoader()
+            Toast.makeText(this, "Couldn't Join Public Chat", Toast.LENGTH_SHORT).show()
+        }
     }
     // endregion
 }
@@ -83,7 +129,9 @@ class EnterChatURLFragment : Fragment() {
     }
 
     private fun joinPublicChatIfPossible() {
-        val chatURL = chatURLEditText.text.trim().toString()
+        val inputMethodManager = context!!.getSystemService(BaseActionBarActivity.INPUT_METHOD_SERVICE) as InputMethodManager
+        inputMethodManager.hideSoftInputFromWindow(chatURLEditText.windowToken, 0)
+        val chatURL = chatURLEditText.text.trim().toString().toLowerCase().replace("http://", "https://")
         (activity!! as JoinPublicChatActivity).joinPublicChatIfPossible(chatURL)
     }
 }