Fix conversation deletion & public chat joining

This commit is contained in:
Niels Andriesse 2020-01-08 10:50:11 +11:00
parent fb3bd26538
commit 7da4f1f6ae
7 changed files with 155 additions and 51 deletions

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contentView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"

View File

@ -1,14 +1,39 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.v4.view.ViewPager <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" 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_width="match_parent"
android:layout_height="match_parent" > android:layout_height="match_parent">
<android.support.design.widget.TabLayout <android.support.v4.view.ViewPager
style="@style/Session.DarkTabLayout" android:id="@+id/viewPager"
android:id="@+id/tabLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/tab_bar_height" /> android:layout_height="match_parent" >
</android.support.v4.view.ViewPager> <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>

View File

@ -37,7 +37,7 @@
<dimen name="large_spacing">24dp</dimen> <dimen name="large_spacing">24dp</dimen>
<dimen name="very_large_spacing">35dp</dimen> <dimen name="very_large_spacing">35dp</dimen>
<dimen name="massive_spacing">64dp</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> <dimen name="onboarding_button_bottom_offset">40dp</dimen>
<!-- Session --> <!-- Session -->

View File

@ -16,6 +16,19 @@
<item name="android:textSize">@dimen/very_large_font_size</item> <item name="android:textSize">@dimen/very_large_font_size</item>
</style> </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"> <style name="Session.DarkTabLayout" parent="Widget.Design.TabLayout">
<item name="tabIndicatorColor">@color/accent</item> <item name="tabIndicatorColor">@color/accent</item>
<item name="tabIndicatorHeight">@dimen/accent_line_thickness</item> <item name="tabIndicatorHeight">@dimen/accent_line_thickness</item>

View File

@ -8,6 +8,7 @@
<item name="colorPrimary">@color/action_bar_background</item> <item name="colorPrimary">@color/action_bar_background</item>
<item name="colorPrimaryDark">@color/action_bar_background</item> <item name="colorPrimaryDark">@color/action_bar_background</item>
<item name="android:navigationBarColor">@color/navigation_bar_background</item> <item name="android:navigationBarColor">@color/navigation_bar_background</item>
<item name="alertDialogTheme">@style/Session.AlertDialog</item>
</style> </style>
<style name="Session.DarkTheme.NoActionBar" parent="@style/Theme.AppCompat.NoActionBar"> <style name="Session.DarkTheme.NoActionBar" parent="@style/Theme.AppCompat.NoActionBar">
@ -15,6 +16,7 @@
<item name="colorPrimary">@color/action_bar_background</item> <item name="colorPrimary">@color/action_bar_background</item>
<item name="colorPrimaryDark">@color/action_bar_background</item> <item name="colorPrimaryDark">@color/action_bar_background</item>
<item name="android:navigationBarColor">@color/navigation_bar_background</item> <item name="android:navigationBarColor">@color/navigation_bar_background</item>
<item name="alertDialogTheme">@style/Session.AlertDialog</item>
</style> </style>
<!-- Session --> <!-- Session -->

View File

@ -1,9 +1,7 @@
package org.thoughtcrime.securesms.loki.redesign.activities package org.thoughtcrime.securesms.loki.redesign.activities
import android.animation.ValueAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.arch.lifecycle.Observer import android.arch.lifecycle.Observer
import android.content.Context
import android.content.Intent import android.content.Intent
import android.database.Cursor import android.database.Cursor
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
@ -11,9 +9,10 @@ import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.os.AsyncTask import android.os.AsyncTask
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.support.design.widget.Snackbar
import android.support.v4.app.LoaderManager import android.support.v4.app.LoaderManager
import android.support.v4.content.Loader import android.support.v4.content.Loader
import android.support.v7.app.AlertDialog
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper 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.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.ConversationActivity import org.thoughtcrime.securesms.conversation.ConversationActivity
import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.database.model.ThreadRecord
import org.thoughtcrime.securesms.loki.getColorWithID import org.thoughtcrime.securesms.loki.getColorWithID
import org.thoughtcrime.securesms.loki.redesign.utilities.push import org.thoughtcrime.securesms.loki.redesign.utilities.push
@ -48,6 +48,24 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady) 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 // Set content view
setContentView(R.layout.activity_home) setContentView(R.layout.activity_home)
// Set custom toolbar // Set custom toolbar
@ -106,7 +124,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
} }
override fun onLongConversationClick(view: ConversationView) { override fun onLongConversationClick(view: ConversationView) {
// TODO: Implement // Do nothing
} }
private fun openConversation(thread: ThreadRecord) { private fun openConversation(thread: ThreadRecord) {
@ -135,7 +153,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
startActivity(intent) 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 { override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
return false return false
@ -143,51 +161,48 @@ class HomeActivity : PassphraseRequiredActionBarActivity, ConversationClickListe
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val builder = AlertDialog.Builder(context) val threadID = (viewHolder as HomeAdapter.ViewHolder).view.thread!!.threadId
builder.setIconAttribute(R.attr.dialog_alert_icon) val threadDatabase = DatabaseFactory.getThreadDatabase(activity)
builder.setTitle("Delete Selected Conversation?") threadDatabase.archiveConversation(threadID)
builder.setMessage("This will permanently delete the selected conversation.") val deleteThread = object : Runnable {
builder.setCancelable(true)
builder.setPositiveButton("Delete") { dialog, _ -> override fun run() {
val threadID = (viewHolder as HomeAdapter.ViewHolder).view.thread!!.threadId AsyncTask.execute {
AsyncTask.execute { threadDatabase.deleteConversation(threadID)
DatabaseFactory.getThreadDatabase(context).deleteConversation(threadID) MessageNotifier.updateNotification(activity)
MessageNotifier.updateNotification(context) }
} }
dialog.dismiss()
} }
builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> val handler = Handler()
val animator = ValueAnimator.ofFloat(viewHolder.itemView.translationX, 0.0f) handler.postDelayed(deleteThread, 5000)
animator.duration = 150 val snackbar = Snackbar.make(activity.contentView, "Conversation Deleted", Snackbar.LENGTH_LONG)
animator.addUpdateListener { snackbar.setAction("Undo") {
update(viewHolder, animator.animatedValue as Float) threadDatabase.unarchiveConversation(threadID)
} handler.removeCallbacks(deleteThread)
animator.start() animate(viewHolder, 0.0f)
dialog.dismiss()
} }
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) { override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dx: Float, dy: Float, actionState: Int, isCurrentlyActive: Boolean) {
if (actionState != ItemTouchHelper.ACTION_STATE_SWIPE) { if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && dx < 0) {
super.onChildDraw(c, recyclerView, viewHolder, dx, dy, actionState, isCurrentlyActive)
} else {
val itemView = viewHolder.itemView val itemView = viewHolder.itemView
if (dx < 0) { animate(viewHolder, dx)
val backgroundPaint = Paint() val backgroundPaint = Paint()
backgroundPaint.color = context.resources.getColorWithID(R.color.destructive, context.theme) 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) 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 icon = BitmapFactory.decodeResource(activity.resources, R.drawable.ic_trash_filled_32)
val iconPaint = Paint() val iconPaint = Paint()
val left = itemView.right.toFloat() - abs(dx) + context.resources.getDimension(R.dimen.medium_spacing) 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 val top = itemView.top.toFloat() + (itemView.bottom.toFloat() - itemView.top.toFloat() - icon.height) / 2
c.drawBitmap(icon, left, top, iconPaint) c.drawBitmap(icon, left, top, iconPaint)
} } else {
update(viewHolder, dx) 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() val alpha = 1.0f - abs(dx) / viewHolder.itemView.width.toFloat()
viewHolder.itemView.alpha = alpha viewHolder.itemView.alpha = alpha
viewHolder.itemView.translationX = dx viewHolder.itemView.translationX = dx

View File

@ -1,17 +1,28 @@
package org.thoughtcrime.securesms.loki.redesign.activities package org.thoughtcrime.securesms.loki.redesign.activities
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v4.app.FragmentPagerAdapter import android.support.v4.app.FragmentPagerAdapter
import android.util.Patterns
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup 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.activity_join_public_chat.*
import kotlinx.android.synthetic.main.fragment_enter_chat_url.* import kotlinx.android.synthetic.main.fragment_enter_chat_url.*
import network.loki.messenger.R 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.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.loki.redesign.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.redesign.fragments.ScanQRCodeWrapperFragment
import org.thoughtcrime.securesms.loki.redesign.fragments.ScanQRCodeWrapperFragmentDelegate import org.thoughtcrime.securesms.loki.redesign.fragments.ScanQRCodeWrapperFragmentDelegate
import org.thoughtcrime.securesms.util.TextSecurePreferences
class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate { class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCodeWrapperFragmentDelegate {
private val adapter = JoinPublicChatActivityAdapter(this) private val adapter = JoinPublicChatActivityAdapter(this)
@ -29,13 +40,48 @@ class JoinPublicChatActivity : PassphraseRequiredActionBarActivity(), ScanQRCode
} }
// endregion // 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 // region Interaction
override fun handleQRCodeScanned(url: String) { override fun handleQRCodeScanned(url: String) {
joinPublicChatIfPossible(url) joinPublicChatIfPossible(url)
} }
fun joinPublicChatIfPossible(url: String) { 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 // endregion
} }
@ -83,7 +129,9 @@ class EnterChatURLFragment : Fragment() {
} }
private fun joinPublicChatIfPossible() { 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) (activity!! as JoinPublicChatActivity).joinPublicChatIfPossible(chatURL)
} }
} }