CFV2 - Implement add to home screen.

This commit is contained in:
Alex Hart 2023-05-30 15:10:23 -03:00 committed by Cody Henthorne
parent be01f2b511
commit 0dd51856d3
3 changed files with 149 additions and 8 deletions

View file

@ -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

View file

@ -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

View file

@ -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) {
}