CFV2 - Implement add to home screen.
This commit is contained in:
parent
be01f2b511
commit
0dd51856d3
3 changed files with 149 additions and 8 deletions
|
@ -8,7 +8,11 @@ package org.thoughtcrime.securesms.conversation.v2
|
|||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityOptions
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
|
@ -37,6 +41,7 @@ import androidx.appcompat.view.ActionMode
|
|||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import androidx.core.view.doOnPreDraw
|
||||
|
@ -63,6 +68,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers
|
|||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import org.signal.core.util.PendingIntentFlags
|
||||
import org.signal.core.util.Result
|
||||
import org.signal.core.util.ThreadUtil
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
|
@ -72,6 +78,7 @@ import org.signal.core.util.logging.Log
|
|||
import org.signal.core.util.orNull
|
||||
import org.signal.libsignal.protocol.InvalidMessageException
|
||||
import org.thoughtcrime.securesms.BlockUnblockDialog
|
||||
import org.thoughtcrime.securesms.GroupMembersDialog
|
||||
import org.thoughtcrime.securesms.LoggingFragment
|
||||
import org.thoughtcrime.securesms.MainActivity
|
||||
import org.thoughtcrime.securesms.MuteDialog
|
||||
|
@ -232,6 +239,7 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
|||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ConversationFragment::class.java)
|
||||
private const val ACTION_PINNED_SHORTCUT = "action_pinned_shortcut"
|
||||
}
|
||||
|
||||
private val args: ConversationIntents.Args by lazy {
|
||||
|
@ -291,6 +299,7 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
|||
|
||||
private var animationsAllowed = false
|
||||
private var actionMode: ActionMode? = null
|
||||
private var pinnedShortcutReceiver: BroadcastReceiver? = null
|
||||
|
||||
private val jumpAndPulseScrollStrategy = object : ScrollToPositionDelegate.ScrollStrategy {
|
||||
override fun performScroll(recyclerView: RecyclerView, layoutManager: LinearLayoutManager, position: Int, smooth: Boolean) {
|
||||
|
@ -388,6 +397,13 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
|||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
if (pinnedShortcutReceiver != null) {
|
||||
requireActivity().unregisterReceiver(pinnedShortcutReceiver)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeConversationThread() {
|
||||
var firstRender = true
|
||||
disposables += viewModel
|
||||
|
@ -1783,7 +1799,29 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
|||
}
|
||||
|
||||
override fun handleAddShortcut() {
|
||||
// TODO [cfv2] - ("Not yet implemented")
|
||||
val recipient: Recipient = viewModel.recipientSnapshot ?: return
|
||||
Log.i(TAG, "Creating home screen shortcut for recipient ${recipient.id}")
|
||||
|
||||
if (pinnedShortcutReceiver == null) {
|
||||
pinnedShortcutReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
toast(
|
||||
toastTextId = R.string.ConversationActivity_added_to_home_screen,
|
||||
toastDuration = Toast.LENGTH_LONG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
requireActivity().registerReceiver(pinnedShortcutReceiver, IntentFilter(ACTION_PINNED_SHORTCUT))
|
||||
}
|
||||
|
||||
viewModel.getContactPhotoIcon(requireContext(), GlideApp.with(this@ConversationFragment))
|
||||
.subscribe { infoCompat ->
|
||||
val intent = Intent(ACTION_PINNED_SHORTCUT)
|
||||
val callback = PendingIntent.getBroadcast(requireContext(), 902, intent, PendingIntentFlags.mutable())
|
||||
ShortcutManagerCompat.requestPinShortcut(requireContext(), infoCompat, callback.intentSender)
|
||||
}
|
||||
.addTo(disposables)
|
||||
}
|
||||
|
||||
override fun handleSearch() {
|
||||
|
@ -1801,16 +1839,13 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment)
|
|||
}
|
||||
|
||||
override fun handleDisplayGroupRecipients() {
|
||||
// TODO [cfv2] - ("Not yet implemented")
|
||||
val recipientSnapshot = viewModel.recipientSnapshot?.takeIf { it.isGroup } ?: return
|
||||
GroupMembersDialog(requireActivity(), recipientSnapshot).display()
|
||||
}
|
||||
|
||||
override fun handleDistributionBroadcastEnabled(menuItem: MenuItem) {
|
||||
// TODO [cfv2] - ("Not yet implemented")
|
||||
}
|
||||
override fun handleDistributionBroadcastEnabled(menuItem: MenuItem) = error("This fragment does not support this action.")
|
||||
|
||||
override fun handleDistributionConversationEnabled(menuItem: MenuItem) {
|
||||
// TODO [cfv2] - ("Not yet implemented")
|
||||
}
|
||||
override fun handleDistributionConversationEnabled(menuItem: MenuItem) = error("This fragment does not support this action.")
|
||||
|
||||
override fun handleManageGroup() {
|
||||
val recipient = viewModel.recipientSnapshot ?: return
|
||||
|
|
|
@ -6,22 +6,32 @@
|
|||
package org.thoughtcrime.securesms.conversation.v2
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.core.Maybe
|
||||
import io.reactivex.rxjava3.core.Observable
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import io.reactivex.rxjava3.core.SingleEmitter
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import org.signal.core.util.StreamUtil
|
||||
import org.signal.core.util.concurrent.SignalExecutors
|
||||
import org.signal.core.util.dp
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.toOptional
|
||||
import org.signal.libsignal.protocol.InvalidMessageException
|
||||
import org.signal.paging.PagedData
|
||||
import org.signal.paging.PagingConfig
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.ShortcutLauncherActivity
|
||||
import org.thoughtcrime.securesms.components.reminder.BubbleOptOutReminder
|
||||
import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder
|
||||
import org.thoughtcrime.securesms.components.reminder.GroupsV1MigrationSuggestionsReminder
|
||||
|
@ -57,6 +67,7 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob
|
|||
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestState
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMessage
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel
|
||||
|
@ -67,6 +78,8 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
|||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||
import org.thoughtcrime.securesms.util.DrawableUtil
|
||||
import org.thoughtcrime.securesms.util.SignalLocalMetrics
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.thoughtcrime.securesms.util.hasTextSlide
|
||||
|
@ -394,6 +407,91 @@ class ConversationRepository(
|
|||
.joinToString("\n")
|
||||
}
|
||||
|
||||
fun getRecipientContactPhotoBitmap(context: Context, glideRequests: GlideRequests, recipient: Recipient): Single<ShortcutInfoCompat> {
|
||||
val fallback = recipient.fallbackContactPhoto.asDrawable(context, recipient.avatarColor, false)
|
||||
|
||||
return Single
|
||||
.create { emitter ->
|
||||
glideRequests
|
||||
.asBitmap()
|
||||
.load(recipient.contactPhoto)
|
||||
.error(fallback)
|
||||
.into(ContactPhotoTarget(recipient.id, emitter))
|
||||
}
|
||||
.flatMap(ContactPhotoResult::transformToFinalBitmap)
|
||||
.map(IconCompat::createWithAdaptiveBitmap)
|
||||
.map {
|
||||
val name = if (recipient.isSelf) context.getString(R.string.note_to_self) else recipient.getDisplayName(context)
|
||||
|
||||
ShortcutInfoCompat.Builder(context, "${recipient.id.serialize()}-${System.currentTimeMillis()}")
|
||||
.setShortLabel(name)
|
||||
.setIcon(it)
|
||||
.setIntent(ShortcutLauncherActivity.createIntent(context, recipient.id))
|
||||
.build()
|
||||
}
|
||||
.subscribeOn(Schedulers.computation())
|
||||
}
|
||||
|
||||
/**
|
||||
* Glide target for a contact photo which expects an error drawable, and publishes
|
||||
* the result to the given emitter.
|
||||
*
|
||||
* The recipient is only used for displaying logging information.
|
||||
*/
|
||||
private class ContactPhotoTarget(
|
||||
private val recipientId: RecipientId,
|
||||
private val emitter: SingleEmitter<ContactPhotoResult>
|
||||
) : CustomTarget<Bitmap>() {
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
requireNotNull(errorDrawable)
|
||||
Log.w(TAG, "Utilizing fallback photo for shortcut for recipient $recipientId")
|
||||
emitter.onSuccess(ContactPhotoResult.DrawableResult(errorDrawable))
|
||||
}
|
||||
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
emitter.onSuccess(ContactPhotoResult.BitmapResult(resource))
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) = Unit
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of the Glide load to get a user's contact photo. This can then be transformed into
|
||||
* something that the Android system likes via [transformToFinalBitmap]
|
||||
*/
|
||||
sealed interface ContactPhotoResult {
|
||||
|
||||
companion object {
|
||||
private val SHORTCUT_ICON_SIZE = if (Build.VERSION.SDK_INT >= 26) 72.dp else (48 + 16 * 2).dp
|
||||
}
|
||||
|
||||
class DrawableResult(private val drawable: Drawable) : ContactPhotoResult {
|
||||
override fun transformToFinalBitmap(): Single<Bitmap> {
|
||||
return Single.create {
|
||||
val bitmap = DrawableUtil.toBitmap(drawable, SHORTCUT_ICON_SIZE, SHORTCUT_ICON_SIZE)
|
||||
it.setCancellable {
|
||||
bitmap.recycle()
|
||||
}
|
||||
it.onSuccess(bitmap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BitmapResult(private val bitmap: Bitmap) : ContactPhotoResult {
|
||||
override fun transformToFinalBitmap(): Single<Bitmap> {
|
||||
return Single.create {
|
||||
val bitmap = BitmapUtil.createScaledBitmap(bitmap, SHORTCUT_ICON_SIZE, SHORTCUT_ICON_SIZE)
|
||||
it.setCancellable {
|
||||
bitmap.recycle()
|
||||
}
|
||||
it.onSuccess(bitmap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun transformToFinalBitmap(): Single<Bitmap>
|
||||
}
|
||||
|
||||
data class MessageCounts(
|
||||
val unread: Int,
|
||||
val mentions: Int
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.conversation.v2
|
|||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
|
@ -43,6 +44,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
|||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestRepository
|
||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestState
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.mms.QuoteModel
|
||||
import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
@ -230,6 +232,12 @@ class ConversationViewModel(
|
|||
.addTo(disposables)
|
||||
}
|
||||
|
||||
fun getContactPhotoIcon(context: Context, glideRequests: GlideRequests): Single<ShortcutInfoCompat> {
|
||||
return recipient.firstOrError().flatMap {
|
||||
repository.getRecipientContactPhotoBitmap(context, glideRequests, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun requestMarkRead(timestamp: Long) {
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue