Update first time navigation screen.

main
Alex Hart 2022-10-27 13:43:52 -03:00 zatwierdzone przez GitHub
rodzic d003dc435a
commit 3600a4818c
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
18 zmienionych plików z 303 dodań i 229 usunięć

Wyświetl plik

@ -516,6 +516,14 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
} }
) )
clickPref(
title = DSLSettingsText.from("Clear first time navigation state"),
isEnabled = true,
onClick = {
SignalStore.storyValues().userHasSeenFirstNavView = false
}
)
clickPref( clickPref(
title = DSLSettingsText.from(R.string.preferences__internal_stories_dialog_launcher), title = DSLSettingsText.from(R.string.preferences__internal_stories_dialog_launcher),
onClick = { onClick = {

Wyświetl plik

@ -1,7 +1,8 @@
package org.thoughtcrime.securesms.stories package org.thoughtcrime.securesms.stories
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Context import android.content.Context
import android.graphics.Canvas
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
@ -10,14 +11,13 @@ import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import com.airbnb.lottie.LottieAnimationView
import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.blurhash.BlurHash import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.components.CornerMask
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.util.visible import org.thoughtcrime.securesms.util.visible
@ -28,20 +28,22 @@ class StoryFirstTimeNavigationView @JvmOverloads constructor(
companion object { companion object {
private const val BLUR_ALPHA = 0x3D private const val BLUR_ALPHA = 0x3D
private const val NO_BLUR_ALPHA = 0xB3 private const val NO_BLUR_ALPHA = 0xCC
} }
init { init {
inflate(context, R.layout.story_first_time_navigation_view, this) inflate(context, R.layout.story_first_time_navigation_view, this)
} }
private val tapToAdvance: LottieAnimationView = findViewById(R.id.edu_tap_icon)
private val swipeUp: LottieAnimationView = findViewById(R.id.edu_swipe_up_icon)
private val swipeRight: LottieAnimationView = findViewById(R.id.edu_swipe_right_icon)
private val blurHashView: ImageView = findViewById(R.id.edu_blur_hash) private val blurHashView: ImageView = findViewById(R.id.edu_blur_hash)
private val overlayView: ImageView = findViewById(R.id.edu_overlay) private val overlayView: ImageView = findViewById(R.id.edu_overlay)
private val gotIt: View = findViewById(R.id.edu_got_it) private val gotIt: View = findViewById(R.id.edu_got_it)
private val close: View = findViewById(R.id.edu_close)
private val cornerMask = CornerMask(this).apply { private var isPlayingAnimations = false
setRadius(DimensionUnit.DP.toPixels(18f).toInt())
}
var callback: Callback? = null var callback: Callback? = null
@ -59,12 +61,14 @@ class StoryFirstTimeNavigationView @JvmOverloads constructor(
hide() hide()
} }
setOnClickListener { } close.setOnClickListener {
} callback?.onCloseClicked()
GlideApp.with(this).clear(blurHashView)
blurHashView.setImageDrawable(null)
hide()
}
override fun dispatchDraw(canvas: Canvas) { setOnClickListener { }
super.dispatchDraw(canvas)
cornerMask.mask(canvas)
} }
fun setBlurHash(blurHash: BlurHash?) { fun setBlurHash(blurHash: BlurHash?) {
@ -105,10 +109,47 @@ class StoryFirstTimeNavigationView @JvmOverloads constructor(
} }
visible = true visible = true
startLottieAnimations()
} }
fun hide() { fun hide() {
visible = false visible = false
endLottieAnimations()
}
private fun startLottieAnimations() {
isPlayingAnimations = true
tapToAdvance.addAnimatorListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
if (isPlayingAnimations) {
swipeUp.playAnimation()
}
}
})
swipeUp.addAnimatorListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
if (isPlayingAnimations) {
swipeRight.playAnimation()
}
}
})
swipeRight.addAnimatorListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
postDelayed({
if (isPlayingAnimations) {
startLottieAnimations()
}
}, 300)
}
})
tapToAdvance.playAnimation()
}
private fun endLottieAnimations() {
isPlayingAnimations = false
} }
private fun isRenderEffectSupported(): Boolean { private fun isRenderEffectSupported(): Boolean {
@ -118,5 +159,6 @@ class StoryFirstTimeNavigationView @JvmOverloads constructor(
interface Callback { interface Callback {
fun userHasSeenFirstNavigationView(): Boolean fun userHasSeenFirstNavigationView(): Boolean
fun onGotItClicked() fun onGotItClicked()
fun onCloseClicked()
} }
} }

Wyświetl plik

@ -6,6 +6,7 @@ import android.media.AudioManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.KeyEvent import android.view.KeyEvent
import android.view.View
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.media.AudioManagerCompat import androidx.media.AudioManagerCompat
@ -16,7 +17,9 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner
import org.thoughtcrime.securesms.stories.StoryViewerArgs import org.thoughtcrime.securesms.stories.StoryViewerArgs
import org.thoughtcrime.securesms.util.FullscreenHelper
import org.thoughtcrime.securesms.util.ServiceUtil import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.ViewUtil
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -34,9 +37,18 @@ class StoryViewerActivity : PassphraseRequiredActivity(), VoiceNoteMediaControll
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
StoryMutePolicy.initialize() StoryMutePolicy.initialize()
Glide.get(this).setMemoryCategory(MemoryCategory.HIGH) Glide.get(this).setMemoryCategory(MemoryCategory.HIGH)
FullscreenHelper.showSystemUI(window)
supportPostponeEnterTransition() supportPostponeEnterTransition()
val root = findViewById<View>(android.R.id.content)
root.setPadding(
0,
ViewUtil.getStatusBarHeight(root),
0,
ViewUtil.getNavigationBarHeight(root)
)
super.onCreate(savedInstanceState, ready) super.onCreate(savedInstanceState, ready)
setContentView(R.layout.fragment_container) setContentView(R.layout.fragment_container)

Wyświetl plik

@ -1,5 +1,8 @@
package org.thoughtcrime.securesms.stories.viewer package org.thoughtcrime.securesms.stories.viewer
import android.graphics.RenderEffect
import android.graphics.Shader
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
@ -8,9 +11,11 @@ import androidx.fragment.app.viewModels
import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stories.StoryViewerArgs import org.thoughtcrime.securesms.stories.StoryViewerArgs
import org.thoughtcrime.securesms.stories.viewer.first.StoryFirstTimeNavigationFragment
import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageArgs import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageArgs
import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageFragment import org.thoughtcrime.securesms.stories.viewer.page.StoryViewerPageFragment
import org.thoughtcrime.securesms.stories.viewer.reply.StoriesSharedElementCrossFaderView import org.thoughtcrime.securesms.stories.viewer.reply.StoriesSharedElementCrossFaderView
@ -116,6 +121,20 @@ class StoryViewerFragment :
viewModel.addHiddenAndRefresh(ids.toSet()) viewModel.addHiddenAndRefresh(ids.toSet())
} else { } else {
viewModel.refresh() viewModel.refresh()
if (!SignalStore.storyValues().userHasSeenFirstNavView) {
StoryFirstTimeNavigationFragment().show(childFragmentManager, null)
}
}
if (Build.VERSION.SDK_INT >= 31) {
lifecycleDisposable += viewModel.isFirstTimeNavigationShowing.subscribe {
if (it) {
requireView().rootView.setRenderEffect(RenderEffect.createBlurEffect(100f, 100f, Shader.TileMode.CLAMP))
} else {
requireView().rootView.setRenderEffect(null)
}
}
} }
} }

Wyświetl plik

@ -53,13 +53,20 @@ class StoryViewerViewModel(
var hasConsumedInitialState = false var hasConsumedInitialState = false
private set private set
private val firstTimeNavigationPublisher: BehaviorSubject<Boolean> = BehaviorSubject.createDefault(false)
val isChildScrolling: Observable<Boolean> = childScrollStatePublisher.distinctUntilChanged() val isChildScrolling: Observable<Boolean> = childScrollStatePublisher.distinctUntilChanged()
val isFirstTimeNavigationShowing: Observable<Boolean> = firstTimeNavigationPublisher.distinctUntilChanged()
fun addHiddenAndRefresh(hidden: Set<RecipientId>) { fun addHiddenAndRefresh(hidden: Set<RecipientId>) {
this.hidden.addAll(hidden) this.hidden.addAll(hidden)
refresh() refresh()
} }
fun setIsDisplayingFirstTimeNavigation(isDisplayingFirstTimeNavigation: Boolean) {
firstTimeNavigationPublisher.onNext(isDisplayingFirstTimeNavigation)
}
fun getHidden(): Set<RecipientId> = hidden fun getHidden(): Set<RecipientId> = hidden
fun setCrossfadeTarget(messageRecord: MmsMessageRecord) { fun setCrossfadeTarget(messageRecord: MmsMessageRecord) {

Wyświetl plik

@ -0,0 +1,86 @@
package org.thoughtcrime.securesms.stories.viewer.first
import android.app.Dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.WindowManager
import androidx.core.app.ActivityCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.viewModels
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.stories.StoryFirstTimeNavigationView
import org.thoughtcrime.securesms.stories.viewer.StoryViewerState
import org.thoughtcrime.securesms.stories.viewer.StoryViewerViewModel
import org.thoughtcrime.securesms.util.LifecycleDisposable
class StoryFirstTimeNavigationFragment : DialogFragment(R.layout.story_viewer_first_time_navigation_stub), StoryFirstTimeNavigationView.Callback {
private val viewModel: StoryViewerViewModel by viewModels(ownerProducer = {
requireParentFragment()
})
private val disposables = LifecycleDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_FRAME, R.style.Signal_DayNight_Dialog_FullScreen)
isCancelable = false
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
if (Build.VERSION.SDK_INT >= 21) {
dialog.window!!.addFlags(
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION or
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS or
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
)
}
return dialog
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
(view as StoryFirstTimeNavigationView).show()
view.callback = this
viewModel.setIsDisplayingFirstTimeNavigation(true)
disposables += viewModel.state.subscribe { state ->
when (state.crossfadeSource) {
is StoryViewerState.CrossfadeSource.ImageUri -> {
view.setBlurHash(state.crossfadeSource.imageBlur)
}
else -> {
view.setBlurHash(null)
}
}
}
}
override fun userHasSeenFirstNavigationView(): Boolean {
return SignalStore.storyValues().userHasSeenFirstNavView
}
override fun onGotItClicked() {
dismissAllowingStateLoss()
SignalStore.storyValues().userHasSeenFirstNavView = true
viewModel.setIsDisplayingFirstTimeNavigation(false)
}
override fun onCloseClicked() {
dismissAllowingStateLoss()
if (viewModel.stateSnapshot.skipCrossfade) {
requireActivity().finish()
} else {
ActivityCompat.finishAfterTransition(requireActivity())
}
}
}

Wyświetl plik

@ -1,61 +0,0 @@
package org.thoughtcrime.securesms.stories.viewer.page
import android.view.ViewStub
import androidx.core.view.isVisible
import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.stories.StoryFirstTimeNavigationView
import org.thoughtcrime.securesms.util.views.Stub
/**
* Specialized stub that allows for early arrival of the blurhash and callback.
*/
class StoryFirstNavigationStub(viewStub: ViewStub) : Stub<StoryFirstTimeNavigationView>(viewStub) {
private var callback: StoryFirstTimeNavigationView.Callback? = null
private var blurHash: BlurHash? = null
fun setCallback(callback: StoryFirstTimeNavigationView.Callback) {
if (resolved()) {
get().callback = callback
} else {
this.callback = callback
}
}
fun setBlurHash(blurHash: BlurHash?) {
if (resolved()) {
get().setBlurHash(blurHash)
} else {
this.blurHash = blurHash
}
}
fun showIfAble(ableToShow: Boolean) {
if (ableToShow) {
get().show()
}
}
fun isVisible(): Boolean {
return resolved() && get().isVisible
}
fun hide() {
if (resolved()) {
get().hide()
}
}
override fun get(): StoryFirstTimeNavigationView {
val needsResolve = !resolved()
val view = super.get()
if (needsResolve) {
view.setBlurHash(blurHash)
view.callback = callback
blurHash = null
callback = null
}
return view
}
}

Wyświetl plik

@ -6,11 +6,8 @@ import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.RenderEffect
import android.graphics.Shader
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.media.AudioManager import android.media.AudioManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.method.ScrollingMovementMethod import android.text.method.ScrollingMovementMethod
import android.view.GestureDetector import android.view.GestureDetector
@ -55,14 +52,12 @@ import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectFor
import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs
import org.thoughtcrime.securesms.database.AttachmentDatabase import org.thoughtcrime.securesms.database.AttachmentDatabase
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mediapreview.MediaPreviewFragment import org.thoughtcrime.securesms.mediapreview.MediaPreviewFragment
import org.thoughtcrime.securesms.mediapreview.VideoControlsDelegate import org.thoughtcrime.securesms.mediapreview.VideoControlsDelegate
import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
import org.thoughtcrime.securesms.stories.StoryFirstTimeNavigationView
import org.thoughtcrime.securesms.stories.StorySlateView import org.thoughtcrime.securesms.stories.StorySlateView
import org.thoughtcrime.securesms.stories.StoryVolumeOverlayView import org.thoughtcrime.securesms.stories.StoryVolumeOverlayView
import org.thoughtcrime.securesms.stories.dialogs.StoryContextMenu import org.thoughtcrime.securesms.stories.dialogs.StoryContextMenu
@ -97,7 +92,6 @@ class StoryViewerPageFragment :
StoryPostFragment.Callback, StoryPostFragment.Callback,
MultiselectForwardBottomSheet.Callback, MultiselectForwardBottomSheet.Callback,
StorySlateView.Callback, StorySlateView.Callback,
StoryFirstTimeNavigationView.Callback,
StoryInfoBottomSheetDialogFragment.OnInfoSheetDismissedListener, StoryInfoBottomSheetDialogFragment.OnInfoSheetDismissedListener,
SafetyNumberBottomSheet.Callbacks { SafetyNumberBottomSheet.Callbacks {
@ -108,7 +102,7 @@ class StoryViewerPageFragment :
private lateinit var viewsAndReplies: MaterialButton private lateinit var viewsAndReplies: MaterialButton
private lateinit var storyCaptionContainer: FrameLayout private lateinit var storyCaptionContainer: FrameLayout
private lateinit var storyContentContainer: FrameLayout private lateinit var storyContentContainer: FrameLayout
private lateinit var storyFirstTimeNavigationViewStub: StoryFirstNavigationStub private lateinit var storyPageContainer: ConstraintLayout
private lateinit var sendingBarTextView: TextView private lateinit var sendingBarTextView: TextView
private lateinit var sendingBar: View private lateinit var sendingBar: View
@ -176,17 +170,16 @@ class StoryViewerPageFragment :
val storyGradientBottom: View = view.findViewById(R.id.story_gradient_bottom) val storyGradientBottom: View = view.findViewById(R.id.story_gradient_bottom)
val storyVolumeOverlayView: StoryVolumeOverlayView = view.findViewById(R.id.story_volume_overlay) val storyVolumeOverlayView: StoryVolumeOverlayView = view.findViewById(R.id.story_volume_overlay)
storyPageContainer = view.findViewById(R.id.story_page_container)
storyContentContainer = view.findViewById(R.id.story_content_container) storyContentContainer = view.findViewById(R.id.story_content_container)
storyCaptionContainer = view.findViewById(R.id.story_caption_container) storyCaptionContainer = view.findViewById(R.id.story_caption_container)
storySlate = view.findViewById(R.id.story_slate) storySlate = view.findViewById(R.id.story_slate)
progressBar = view.findViewById(R.id.progress) progressBar = view.findViewById(R.id.progress)
viewsAndReplies = view.findViewById(R.id.views_and_replies_bar) viewsAndReplies = view.findViewById(R.id.views_and_replies_bar)
storyFirstTimeNavigationViewStub = StoryFirstNavigationStub(view.findViewById(R.id.story_first_time_nav_stub))
sendingBarTextView = view.findViewById(R.id.sending_text_view) sendingBarTextView = view.findViewById(R.id.sending_text_view)
sendingBar = view.findViewById(R.id.sending_bar) sendingBar = view.findViewById(R.id.sending_bar)
storySlate.callback = this storySlate.callback = this
storyFirstTimeNavigationViewStub.setCallback(this)
chrome = listOf( chrome = listOf(
closeView, closeView,
@ -322,6 +315,10 @@ class StoryViewerPageFragment :
viewModel.setIsUserScrollingChild(it) viewModel.setIsUserScrollingChild(it)
} }
lifecycleDisposable += sharedViewModel.isFirstTimeNavigationShowing.subscribe {
viewModel.setIsDisplayingFirstTimeNavigation(it)
}
lifecycleDisposable += storyVolumeViewModel.state.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread()).subscribe { volumeState -> lifecycleDisposable += storyVolumeViewModel.state.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread()).subscribe { volumeState ->
if (volumeState.isMuted) { if (volumeState.isMuted) {
videoControlsDelegate.mute() videoControlsDelegate.mute()
@ -384,7 +381,6 @@ class StoryViewerPageFragment :
presentDate(date, post) presentDate(date, post)
presentDistributionList(distributionList, post) presentDistributionList(distributionList, post)
presentCaption(caption, largeCaption, largeCaptionOverlay, post) presentCaption(caption, largeCaption, largeCaptionOverlay, post)
presentBlur(post)
val durations: Map<Int, Long> = state.posts val durations: Map<Int, Long> = state.posts
.mapIndexed { index, storyPost -> .mapIndexed { index, storyPost ->
@ -428,37 +424,20 @@ class StoryViewerPageFragment :
resumeProgress() resumeProgress()
} }
val wasDisplayingNavigationView = storyFirstTimeNavigationViewStub.isVisible()
when { when {
state.hideChromeImmediate -> { state.hideChromeImmediate -> {
hideChromeImmediate() hideChromeImmediate()
storyCaptionContainer.visible = false storyCaptionContainer.visible = false
storyFirstTimeNavigationViewStub.hide()
} }
state.hideChrome -> { state.hideChrome -> {
hideChrome() hideChrome()
storyCaptionContainer.visible = true storyCaptionContainer.visible = true
storyFirstTimeNavigationViewStub.showIfAble(!SignalStore.storyValues().userHasSeenFirstNavView)
} }
else -> { else -> {
showChrome() showChrome()
storyCaptionContainer.visible = true storyCaptionContainer.visible = true
storyFirstTimeNavigationViewStub.showIfAble(!SignalStore.storyValues().userHasSeenFirstNavView)
} }
} }
val isDisplayingNavigationView = storyFirstTimeNavigationViewStub.isVisible()
if (isDisplayingNavigationView && Build.VERSION.SDK_INT >= 31) {
hideChromeImmediate()
storyContentContainer.setRenderEffect(RenderEffect.createBlurEffect(100f, 100f, Shader.TileMode.CLAMP))
} else if (Build.VERSION.SDK_INT >= 31) {
storyContentContainer.setRenderEffect(null)
}
if (wasDisplayingNavigationView xor isDisplayingNavigationView) {
viewModel.setIsDisplayingFirstTimeNavigation(storyFirstTimeNavigationViewStub.isVisible())
}
} }
timeoutDisposable.bindTo(viewLifecycleOwner) timeoutDisposable.bindTo(viewLifecycleOwner)
@ -590,7 +569,7 @@ class StoryViewerPageFragment :
card: CardView card: CardView
) { ) {
val constraintSet = ConstraintSet() val constraintSet = ConstraintSet()
constraintSet.clone(requireView() as ConstraintLayout) constraintSet.clone(storyPageContainer)
when (StoryDisplay.getStoryDisplay(resources.displayMetrics.widthPixels.toFloat(), resources.displayMetrics.heightPixels.toFloat())) { when (StoryDisplay.getStoryDisplay(resources.displayMetrics.widthPixels.toFloat(), resources.displayMetrics.heightPixels.toFloat())) {
StoryDisplay.LARGE -> { StoryDisplay.LARGE -> {
@ -613,7 +592,7 @@ class StoryViewerPageFragment :
} }
} }
constraintSet.applyTo(requireView() as ConstraintLayout) constraintSet.applyTo(storyPageContainer)
} }
private fun resumeProgress() { private fun resumeProgress() {
@ -781,13 +760,6 @@ class StoryViewerPageFragment :
distributionList.visible = storyPost.distributionList != null && !storyPost.distributionList.isMyStory distributionList.visible = storyPost.distributionList != null && !storyPost.distributionList.isMyStory
} }
private fun presentBlur(storyPost: StoryPost) {
val record = storyPost.conversationMessage.messageRecord as? MediaMmsMessageRecord
val blurHash = record?.slideDeck?.thumbnailSlide?.placeholderBlur
storyFirstTimeNavigationViewStub.setBlurHash(blurHash)
}
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
private fun presentCaption(caption: TextView, largeCaption: TextView, largeCaptionOverlay: View, storyPost: StoryPost) { private fun presentCaption(caption: TextView, largeCaption: TextView, largeCaptionOverlay: View, storyPost: StoryPost) {
val displayBody: String = if (storyPost.content is StoryPost.Content.AttachmentContent) { val displayBody: String = if (storyPost.content is StoryPost.Content.AttachmentContent) {
@ -1284,15 +1256,6 @@ class StoryViewerPageFragment :
sharedViewModel.setContentIsReady() sharedViewModel.setContentIsReady()
} }
override fun userHasSeenFirstNavigationView(): Boolean {
return SignalStore.storyValues().userHasSeenFirstNavView
}
override fun onGotItClicked() {
SignalStore.storyValues().userHasSeenFirstNavView = true
viewModel.setIsDisplayingFirstTimeNavigation(false)
}
override fun onInfoSheetDismissed() { override fun onInfoSheetDismissed() {
viewModel.setIsDisplayingInfoDialog(false) viewModel.setIsDisplayingInfoDialog(false)
} }

Wyświetl plik

@ -24,7 +24,7 @@ data class StoryViewerPlaybackState(
val isUserScaling: Boolean = false, val isUserScaling: Boolean = false,
val isDisplayingPartialSendDialog: Boolean = false val isDisplayingPartialSendDialog: Boolean = false
) { ) {
val hideChromeImmediate: Boolean = isRunningSharedElementAnimation val hideChromeImmediate: Boolean = isRunningSharedElementAnimation || isDisplayingFirstTimeNavigation
val hideChrome: Boolean = isRunningSharedElementAnimation || val hideChrome: Boolean = isRunningSharedElementAnimation ||
isUserLongTouching || isUserLongTouching ||

Wyświetl plik

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:viewBindingIgnore="true">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

Wyświetl plik

@ -2,6 +2,7 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/story_page_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipChildren="false" android:clipChildren="false"
@ -295,13 +296,4 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<ViewStub
android:id="@+id/story_first_time_nav_stub"
android:layout_width="match_parent"
android:layout_height="0dp"
android:inflatedId="@+id/story_first_time_navigation_view"
android:layout="@layout/story_viewer_first_time_navigation_stub"
app:layout_constraintBottom_toBottomOf="@id/story_content_card_touch_interceptor"
app:layout_constraintTop_toTopOf="@id/story_content_card_touch_interceptor" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -8,115 +8,95 @@
<ImageView <ImageView
android:id="@+id/edu_blur_hash" android:id="@+id/edu_blur_hash"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
android:importantForAccessibility="no" /> android:importantForAccessibility="no"
app:layout_constraintDimensionRatio="9:16"
app:tint="@color/core_white" />
<ImageView <ImageView
android:id="@+id/edu_overlay" android:id="@+id/edu_overlay"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:src="@color/transparent_black_70" /> android:src="@color/transparent_black_80" />
<androidx.constraintlayout.widget.ConstraintLayout <com.airbnb.lottie.LottieAnimationView
android:id="@+id/button_container" android:id="@+id/edu_tap_icon"
android:layout_width="wrap_content" android:layout_width="72dp"
android:layout_height="wrap_content" android:layout_height="72dp"
android:layout_marginHorizontal="32dp" android:importantForAccessibility="no"
app:layout_constrainedHeight="true" app:layout_constraintBottom_toTopOf="@id/edu_tap_label"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@id/edu_got_it"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:lottie_rawRes="@raw/stories_tap_to_advance" />
<ImageView <TextView
android:id="@+id/edu_tap_icon" android:id="@+id/edu_tap_label"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp" android:layout_marginTop="16dp"
android:importantForAccessibility="no" android:text="@string/StoryFirstTimeNavigationView__tap_to_advance"
app:layout_constraintBottom_toTopOf="@id/edu_swipe_up_icon" android:textAppearance="@style/Signal.Text.BodyLarge"
app:layout_constraintEnd_toStartOf="@id/edu_tap_label" android:textColor="@color/core_white"
app:layout_constraintHorizontal_bias="0" app:layout_constrainedWidth="true"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintBottom_toTopOf="@id/edu_swipe_up_icon"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_tap_72" app:layout_constraintTop_toBottomOf="@id/edu_tap_icon" />
app:tint="@color/core_white" />
<TextView <com.airbnb.lottie.LottieAnimationView
android:id="@+id/edu_tap_label" android:id="@+id/edu_swipe_up_icon"
android:layout_width="wrap_content" android:layout_width="72dp"
android:layout_height="wrap_content" android:layout_height="72dp"
android:text="@string/StoryFirstTimeNavigationView__tap_to_advance" android:layout_marginTop="48dp"
android:textAppearance="@style/Signal.Text.BodyLarge" android:importantForAccessibility="no"
android:textColor="@color/core_white" app:layout_constraintBottom_toTopOf="@id/edu_swipe_up_label"
app:layout_constrainedWidth="true" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/edu_tap_icon" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/edu_tap_label"
app:layout_constraintStart_toEndOf="@id/edu_tap_icon" app:lottie_rawRes="@raw/stories_swipe_up" />
app:layout_constraintTop_toTopOf="@id/edu_tap_icon" />
<ImageView <TextView
android:id="@+id/edu_swipe_up_icon" android:id="@+id/edu_swipe_up_label"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="48dp" android:layout_marginTop="16dp"
android:layout_marginEnd="16dp" android:text="@string/StoryFirstTimeNavigationView__swipe_up_to_skip"
android:importantForAccessibility="no" android:textAppearance="@style/Signal.Text.BodyLarge"
app:layout_constraintBottom_toTopOf="@id/edu_swipe_right_icon" android:textColor="@color/core_white"
app:layout_constraintEnd_toStartOf="@id/edu_swipe_up_label" app:layout_constrainedWidth="true"
app:layout_constraintHorizontal_bias="0" app:layout_constraintBottom_toTopOf="@id/edu_swipe_right_icon"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/edu_tap_icon" app:layout_constraintTop_toBottomOf="@id/edu_swipe_up_icon" />
app:srcCompat="@drawable/ic_swipe_up_72"
app:tint="@color/core_white" />
<TextView <com.airbnb.lottie.LottieAnimationView
android:id="@+id/edu_swipe_up_label" android:id="@+id/edu_swipe_right_icon"
android:layout_width="wrap_content" android:layout_width="72dp"
android:layout_height="wrap_content" android:layout_height="72dp"
android:text="@string/StoryFirstTimeNavigationView__swipe_up_to_skip" android:layout_marginTop="48dp"
android:textAppearance="@style/Signal.Text.BodyLarge" android:importantForAccessibility="no"
android:textColor="@color/core_white" app:layout_constraintBottom_toTopOf="@id/edu_swipe_right_label"
app:layout_constrainedWidth="true" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="@id/edu_swipe_up_icon" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/edu_swipe_up_label"
app:layout_constraintStart_toEndOf="@id/edu_swipe_up_icon" app:lottie_rawRes="@raw/stories_swipe_right" />
app:layout_constraintTop_toTopOf="@id/edu_swipe_up_icon" />
<ImageView
android:id="@+id/edu_swipe_right_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:layout_marginEnd="16dp"
android:importantForAccessibility="no"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/edu_swipe_right_label"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/edu_swipe_up_icon"
app:srcCompat="@drawable/ic_swipe_right_72"
app:tint="@color/core_white" />
<TextView
android:id="@+id/edu_swipe_right_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/StoryFirstTimeNavigationView__swipe_right_to_exit"
android:textAppearance="@style/Signal.Text.BodyLarge"
android:textColor="@color/core_white"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="@id/edu_swipe_right_icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/edu_swipe_right_icon"
app:layout_constraintTop_toTopOf="@id/edu_swipe_right_icon" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/edu_swipe_right_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/StoryFirstTimeNavigationView__swipe_right_to_exit"
android:textAppearance="@style/Signal.Text.BodyLarge"
android:textColor="@color/core_white"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/edu_swipe_right_icon" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/edu_got_it" android:id="@+id/edu_got_it"
@ -129,6 +109,17 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_container" /> app:layout_constraintTop_toBottomOf="@id/edu_swipe_right_label" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/edu_close"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="4dp"
android:layout_marginTop="60dp"
android:contentDescription="@string/Material3SearchToolbar__close"
android:scaleType="centerInside"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_x_white" />
</merge> </merge>

Wyświetl plik

@ -2,8 +2,5 @@
<org.thoughtcrime.securesms.stories.StoryFirstTimeNavigationView xmlns:android="http://schemas.android.com/apk/res/android" <org.thoughtcrime.securesms.stories.StoryFirstTimeNavigationView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:viewBindingIgnore="true" tools:viewBindingIgnore="true"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent" />
app:layout_constraintBottom_toBottomOf="@id/story_content_card_touch_interceptor"
app:layout_constraintTop_toTopOf="@id/story_content_card_touch_interceptor" />

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -117,6 +117,7 @@ class StoryFirstTimeNavigationViewTest {
testSubject.callback = object : StoryFirstTimeNavigationView.Callback { testSubject.callback = object : StoryFirstTimeNavigationView.Callback {
override fun userHasSeenFirstNavigationView(): Boolean = true override fun userHasSeenFirstNavigationView(): Boolean = true
override fun onGotItClicked() = error("Unused") override fun onGotItClicked() = error("Unused")
override fun onCloseClicked() = error("Unused")
} }
testSubject.setBlurHash(BlurHash.parseOrNull("0000")!!) testSubject.setBlurHash(BlurHash.parseOrNull("0000")!!)
@ -150,6 +151,7 @@ class StoryFirstTimeNavigationViewTest {
testSubject.callback = object : StoryFirstTimeNavigationView.Callback { testSubject.callback = object : StoryFirstTimeNavigationView.Callback {
override fun userHasSeenFirstNavigationView(): Boolean = true override fun userHasSeenFirstNavigationView(): Boolean = true
override fun onGotItClicked() = error("Unused") override fun onGotItClicked() = error("Unused")
override fun onCloseClicked() = error("Unused")
} }
testSubject.show() testSubject.show()