Implement Beta opt-in mechanism to inform users about threads.

This commit is contained in:
ariskotsomitopoulos 2022-04-04 19:51:47 +03:00
parent 3aac59a5ae
commit 031c1b409b
8 changed files with 138 additions and 3 deletions

View file

@ -71,6 +71,9 @@ abstract class BottomSheetActionItem : VectorEpoxyModel<BottomSheetActionItem.Ho
@EpoxyAttribute
var destructive = false
@EpoxyAttribute
var showBetaLabel = false
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
lateinit var listener: ClickListener
@ -106,6 +109,7 @@ abstract class BottomSheetActionItem : VectorEpoxyModel<BottomSheetActionItem.Ho
} else {
holder.text.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
}
holder.betaLabel.isVisible = showBetaLabel
}
class Holder : VectorEpoxyHolder() {
@ -113,5 +117,6 @@ abstract class BottomSheetActionItem : VectorEpoxyModel<BottomSheetActionItem.Ho
val icon by bind<ImageView>(R.id.actionIcon)
val text by bind<TextView>(R.id.actionTitle)
val selected by bind<ImageView>(R.id.actionSelected)
val betaLabel by bind<TextView>(R.id.actionBetaTextView)
}
}

View file

@ -25,6 +25,7 @@ import android.os.Build
import android.os.Bundle
import android.text.Spannable
import android.text.format.DateUtils
import android.text.method.LinkMovementMethod
import android.view.HapticFeedbackConstants
import android.view.KeyEvent
import android.view.LayoutInflater
@ -169,6 +170,7 @@ import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.home.room.detail.views.RoomDetailLazyLoadedViews
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.app.features.home.room.threads.ThreadsManager
import im.vector.app.features.home.room.threads.arguments.ThreadTimelineArgs
import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.PillImageSpan
@ -251,6 +253,7 @@ class TimelineFragment @Inject constructor(
private val notificationDrawerManager: NotificationDrawerManager,
private val eventHtmlRenderer: EventHtmlRenderer,
private val vectorPreferences: VectorPreferences,
private val threadsManager: ThreadsManager,
private val colorProvider: ColorProvider,
private val dimensionConverter: DimensionConverter,
private val userPreferencesProvider: UserPreferencesProvider,
@ -2199,7 +2202,7 @@ class TimelineFragment @Inject constructor(
}
is EventSharedAction.ReplyInThread -> {
if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) {
navigateToThreadTimeline(action.eventId, action.startsThread)
onReplyInThreadClicked(action)
} else {
requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit)
}
@ -2355,6 +2358,14 @@ class TimelineFragment @Inject constructor(
.show()
}
private fun onReplyInThreadClicked(action: EventSharedAction.ReplyInThread) {
if (vectorPreferences.areThreadMessagesEnabled()) {
navigateToThreadTimeline(action.eventId, action.startsThread)
} else {
displayThreadsBetaNotice()
}
}
/**
* Navigate to Threads timeline for the specified rootThreadEventId
* using the ThreadsActivity
@ -2374,6 +2385,29 @@ class TimelineFragment @Inject constructor(
}
}
/**
* Display a dialog that will let the user to enable threads
*/
private fun displayThreadsBetaNotice() =
activity?.let {
MaterialAlertDialogBuilder(it)
.setTitle(R.string.threads_beta_enable_notice_title)
.setMessage(threadsManager.getBetaEnableThreadsMessage())
.setCancelable(true)
.setNegativeButton(R.string.action_not_now) { _, _ -> }
.setPositiveButton(R.string.action_try_it_out) { _, _ ->
timelineViewModel.onCleared() // We should first clear our viewModel
threadsManager.enableThreadsAndRestart(it)
}
.show()
?.findViewById<TextView>(android.R.id.message)
?.apply {
linksClickable = true
movementMethod = LinkMovementMethod.getInstance()
}
}
/**
* Navigate to Threads list for the current room
* using the ThreadsActivity

View file

@ -43,6 +43,7 @@ import im.vector.app.features.location.INITIAL_MAP_ZOOM_IN_TIMELINE
import im.vector.app.features.location.UrlMapProvider
import im.vector.app.features.location.toLocationData
import im.vector.app.features.media.ImageContentRenderer
import im.vector.app.features.settings.VectorPreferences
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.orTrue
@ -64,6 +65,7 @@ class MessageActionsEpoxyController @Inject constructor(
private val errorFormatter: ErrorFormatter,
private val spanUtils: SpanUtils,
private val eventDetailsFormatter: EventDetailsFormatter,
private val vectorPreferences: VectorPreferences,
private val dateFormatter: VectorDateFormatter,
private val urlMapProvider: UrlMapProvider,
private val locationPinProvider: LocationPinProvider
@ -187,6 +189,8 @@ class MessageActionsEpoxyController @Inject constructor(
id("separator_$index")
}
} else {
val showBetaLabel = action.shouldShowBetaLabel()
bottomSheetActionItem {
id("action_$index")
iconRes(action.iconResId)
@ -195,6 +199,7 @@ class MessageActionsEpoxyController @Inject constructor(
expanded(state.expendedReportContentMenu)
listener { host.listener?.didSelectMenuAction(action) }
destructive(action.destructive)
showBetaLabel(showBetaLabel)
}
if (action is EventSharedAction.ReportContent && state.expendedReportContentMenu) {
@ -217,6 +222,9 @@ class MessageActionsEpoxyController @Inject constructor(
}
}
private fun EventSharedAction.shouldShowBetaLabel(): Boolean =
this is EventSharedAction.ReplyInThread && !vectorPreferences.areThreadMessagesEnabled()
interface MessageActionsEpoxyControllerListener : TimelineEventController.UrlClickCallback {
fun didSelectMenuAction(eventAction: EventSharedAction)
}

View file

@ -450,7 +450,8 @@ class MessageActionsViewModel @AssistedInject constructor(
private fun canReplyInThread(event: TimelineEvent,
messageContent: MessageContent?,
actionPermissions: ActionPermissions): Boolean {
if (!vectorPreferences.areThreadMessagesEnabled()) return false
/* We let reply in thread visible even if threads are not enabled, with an enhanced flow to attract users */
// if (!vectorPreferences.areThreadMessagesEnabled()) return false
if (initialState.isFromThreadTimeline) return false
if (event.root.isThread()) return false
if (event.root.getClearType() != EventType.MESSAGE &&

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.home.room.threads
import android.app.Activity
import android.content.Context
import android.text.Spanned
import androidx.core.text.HtmlCompat
import im.vector.app.R
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.internal.database.lightweight.LightweightSettingsStorage
import javax.inject.Inject
/**
* The class is responsible for handling thread specific tasks
*/
class ThreadsManager @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val lightweightSettingsStorage: LightweightSettingsStorage,
private val context: Context
) {
companion object {
const val learnMoreUrl = "https://element.io/help#threads"
}
/**
* Enable threads and invoke an initial sync. The initial sync is mandatory in order to change
* the already saved DB schema for already received messages
*/
fun enableThreadsAndRestart(activity: Activity) {
vectorPreferences.setThreadMessagesEnabled()
lightweightSettingsStorage.setThreadMessagesEnabled(vectorPreferences.areThreadMessagesEnabled())
MainActivity.restartApp(activity, MainActivityArgs(clearCache = true))
}
/**
* Generates and return an Html spanned string to be rendered especially in dialogs
*/
fun getBetaEnableThreadsMessage(): Spanned {
val href = "<a href='$learnMoreUrl'>Learn more</a>.<br><br>"
val message = context.getString(R.string.threads_beta_enable_notice_message, href)
return HtmlCompat.fromHtml(message, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
}

View file

@ -27,7 +27,6 @@ import javax.inject.Inject
class VectorSettingsLabsFragment @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val lightweightSettingsStorage: LightweightSettingsStorage
) : VectorSettingsBaseFragment() {
override var titleRes = R.string.room_settings_labs_pref_title

View file

@ -82,4 +82,27 @@
tools:ignore="MissingPrefix"
tools:visibility="visible" />
<TextView
android:id="@+id/actionBetaTextView"
style="@style/Widget.Vector.TextView.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/notification_badge"
android:backgroundTint="@color/palette_azure"
android:gravity="center"
android:paddingStart="10dp"
android:paddingTop="3dp"
android:paddingEnd="10dp"
android:layout_marginEnd="1dp"
android:paddingBottom="3dp"
android:visibility="gone"
tools:visibility="visible"
android:text="@string/beta_title_bottom_sheet_action"
android:textColor="@color/palette_white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/actionSelected"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -360,6 +360,7 @@
<string name="action_enable">Enable</string>
<string name="action_disable">Disable</string>
<string name="action_not_now">Not now</string>
<string name="action_try_it_out">Try it out</string>
<string name="action_agree">Agree</string>
<string name="action_change">"Change"</string>
<string name="action_remove">Remove</string>
@ -734,6 +735,8 @@
<string name="search_thread_from_a_thread">From a Thread</string>
<string name="threads_notice_migration_title">Threads Approaching Beta 🎉</string>
<string name="threads_notice_migration_message">Were getting closer to releasing a public Beta for Threads.\n\nAs we prepare for it, we need to make some changes: threads created before this point will be displayed as regular replies.\n\nThis will be a one-off transition as Threads are now part of the Matrix specification.</string>
<string name="threads_beta_enable_notice_title">Threads Beta</string>
<string name="threads_beta_enable_notice_message">Threads help keep your conversations on-topic and easy to track. %sEnabling threads will refresh the app. This may take longer for some accounts.</string>
<!-- Search -->
<string name="search_hint">Search</string>
@ -1643,6 +1646,7 @@
<string name="send_suggestion_sent">Thanks, the suggestion has been successfully sent</string>
<string name="send_suggestion_failed">The suggestion failed to be sent (%s)</string>
<string name="beta_title_bottom_sheet_action">BETA</string>
<string name="send_feedback_space_title">Spaces feedback</string>
<string name="feedback">Feedback</string>
<string name="send_feedback_space_info">Youre using a beta version of spaces. Your feedback will help inform the next versions. Your platform and username will be noted to help us use your feedback as much as we can.</string>