mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Implement Beta opt-in mechanism to inform users about threads.
This commit is contained in:
parent
3aac59a5ae
commit
031c1b409b
8 changed files with 138 additions and 3 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">We’re 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">You’re 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>
|
||||
|
|
Loading…
Reference in a new issue