Option to open chat without reading

Change-Id: I039bc78ef12975ebd384680f0c2634a8776e0398
This commit is contained in:
SpiritCroc 2022-03-22 08:55:25 +01:00
parent 7723b45993
commit 3f0920cc23
13 changed files with 65 additions and 11 deletions

View file

@ -19,6 +19,7 @@ Here you can find some extra features and changes compared to Element Android (w
- Possibility to open rooms at first unread message instead of at the bottom of the chat - Possibility to open rooms at first unread message instead of at the bottom of the chat
- Possibility to hide the voice message button in the composer - Possibility to hide the voice message button in the composer
- Experimental feature to switch between top-level spaces by swiping the room list - Experimental feature to switch between top-level spaces by swiping the room list
- Option to open a room without marking anything as read automatically
- Remember across app restarts which categories in the chat overview are expanded or collapsed - Remember across app restarts which categories in the chat overview are expanded or collapsed
- Message count passed to the notification badge (visible next to the launcher icon on recent Android versions) - Message count passed to the notification badge (visible next to the launcher icon on recent Android versions)
- Bigger stickers - Bigger stickers

View file

@ -57,6 +57,7 @@ data class RoomDetailViewState(
val asyncRoomSummary: Async<RoomSummary> = Uninitialized, val asyncRoomSummary: Async<RoomSummary> = Uninitialized,
val powerLevelsHelper: PowerLevelsHelper? = null, val powerLevelsHelper: PowerLevelsHelper? = null,
val openAtFirstUnread: Boolean? = null, val openAtFirstUnread: Boolean? = null,
val openAnonymously: Boolean = false,
val activeRoomWidgets: Async<List<Widget>> = Uninitialized, val activeRoomWidgets: Async<List<Widget>> = Uninitialized,
val formattedTypingUsers: String? = null, val formattedTypingUsers: String? = null,
val tombstoneEvent: Event? = null, val tombstoneEvent: Event? = null,
@ -87,6 +88,7 @@ data class RoomDetailViewState(
// Also highlight the target event, if any // Also highlight the target event, if any
highlightedEventId = args.eventId, highlightedEventId = args.eventId,
openAtFirstUnread = args.openAtFirstUnread, openAtFirstUnread = args.openAtFirstUnread,
openAnonymously = args.openAnonymously,
switchToParentSpace = args.switchToParentSpace, switchToParentSpace = args.switchToParentSpace,
rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId rootThreadEventId = args.threadTimelineArgs?.rootThreadEventId
) )

View file

@ -197,17 +197,17 @@ class TimelineViewModel @AssistedInject constructor(
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
if (loadRoomAtFirstUnread()) { if (loadRoomAtFirstUnread()) {
if (vectorPreferences.readReceiptFollowsReadMarker()) { if (vectorPreferences.readReceiptFollowsReadMarker()) {
tryOrNull { room.setMarkedUnreadFlag(false) } tryOrNullAnon { room.setMarkedUnreadFlag(false) }
} else { } else {
tryOrNull { room.setMarkedUnread(false) } tryOrNullAnon { room.setMarkedUnread(false) }
} }
} else { } else {
tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) } tryOrNullAnon { room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) }
} }
} }
// Inform the SDK that the room is displayed // Inform the SDK that the room is displayed
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
tryOrNull { session.onRoomDisplayed(initialState.roomId) } tryOrNullAnon { session.onRoomDisplayed(initialState.roomId) }
} }
callManager.addProtocolsCheckerListener(this) callManager.addProtocolsCheckerListener(this)
callManager.checkForProtocolsSupportIfNeeded() callManager.checkForProtocolsSupportIfNeeded()
@ -371,6 +371,9 @@ class TimelineViewModel @AssistedInject constructor(
* This is a local implementation has nothing to do with APIs * This is a local implementation has nothing to do with APIs
*/ */
private fun markThreadTimelineAsReadLocal() { private fun markThreadTimelineAsReadLocal() {
if (initialState.openAnonymously) {
return
}
initialState.rootThreadEventId?.let { initialState.rootThreadEventId?.let {
session.coroutineScope.launch { session.coroutineScope.launch {
room.markThreadAsRead(it) room.markThreadAsRead(it)
@ -658,9 +661,9 @@ class TimelineViewModel @AssistedInject constructor(
mostRecentDisplayedEvent?.root?.eventId?.also { mostRecentDisplayedEvent?.root?.eventId?.also {
session.coroutineScope.launch(NonCancellable) { session.coroutineScope.launch(NonCancellable) {
rmDimber.i{"set RM and RR to $it"} rmDimber.i{"set RM and RR to $it"}
tryOrNull { room.setReadMarker(it) } tryOrNullAnon { room.setReadMarker(it) }
if (loadRoomAtFirstUnread()) { if (loadRoomAtFirstUnread()) {
tryOrNull { room.setReadReceipt(it) } tryOrNullAnon { room.setReadReceipt(it) }
} }
} }
} }
@ -981,7 +984,7 @@ class TimelineViewModel @AssistedInject constructor(
} }
bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId -> bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId ->
session.coroutineScope.launch { session.coroutineScope.launch {
tryOrNull { room.setReadReceipt(eventId) } tryOrNullAnon { room.setReadReceipt(eventId) }
} }
} }
} }
@ -998,7 +1001,7 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleMarkAllAsRead() { private fun handleMarkAllAsRead() {
setState { copy(unreadState = UnreadState.HasNoUnread) } setState { copy(unreadState = UnreadState.HasNoUnread) }
viewModelScope.launch { viewModelScope.launch {
tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.BOTH) } tryOrNullAnon { room.markAsRead(ReadService.MarkAsReadParams.BOTH) }
} }
} }
@ -1270,4 +1273,11 @@ class TimelineViewModel @AssistedInject constructor(
private fun loadRoomAtFirstUnread(): Boolean { private fun loadRoomAtFirstUnread(): Boolean {
return initialState.openAtFirstUnread ?: vectorPreferences.loadRoomAtFirstUnread() return initialState.openAtFirstUnread ?: vectorPreferences.loadRoomAtFirstUnread()
} }
private inline fun <A>tryOrNullAnon(operation: () -> A): A? {
if (initialState.openAnonymously) {
return null
}
return tryOrNull { operation() }
}
} }

View file

@ -28,6 +28,7 @@ data class TimelineArgs(
val sharedData: SharedData? = null, val sharedData: SharedData? = null,
val openShareSpaceForId: String? = null, val openShareSpaceForId: String? = null,
val openAtFirstUnread: Boolean? = null, val openAtFirstUnread: Boolean? = null,
val openAnonymously: Boolean = false,
val threadTimelineArgs: ThreadTimelineArgs? = null, val threadTimelineArgs: ThreadTimelineArgs? = null,
val switchToParentSpace: Boolean = false, val switchToParentSpace: Boolean = false,
val isInviteAlreadyAccepted: Boolean = false val isInviteAlreadyAccepted: Boolean = false

View file

@ -453,6 +453,9 @@ class RoomListFragment @Inject constructor(
is RoomListQuickActionsSharedAction.OpenAtBottom -> { is RoomListQuickActionsSharedAction.OpenAtBottom -> {
navigator.openRoom(requireActivity(), quickAction.roomId, openAtFirstUnread = false) navigator.openRoom(requireActivity(), quickAction.roomId, openAtFirstUnread = false)
} }
is RoomListQuickActionsSharedAction.OpenAnonymous -> {
navigator.openRoom(requireActivity(), quickAction.roomId, openAnonymously = true)
}
is RoomListQuickActionsSharedAction.Leave -> { is RoomListQuickActionsSharedAction.Leave -> {
promptLeaveRoom(quickAction.roomId) promptLeaveRoom(quickAction.roomId)
} }

View file

@ -76,6 +76,10 @@ class RoomListQuickActionsEpoxyController @Inject constructor(
} }
} }
if (vectorPreferences.showOpenAnonymous()) {
RoomListQuickActionsSharedAction.OpenAnonymous(roomSummary.roomId).toBottomSheetItem("action_open_anonymous")
}
if (vectorPreferences.loadRoomAtFirstUnread()) { if (vectorPreferences.loadRoomAtFirstUnread()) {
// TODO can we check if position of roomSummary.readMarkerId is below or equal to // TODO can we check if position of roomSummary.readMarkerId is below or equal to
// roomSummary.latestPreviewableOriginalContentEvent, and hide this otherwise? // roomSummary.latestPreviewableOriginalContentEvent, and hide this otherwise?

View file

@ -42,6 +42,11 @@ sealed class RoomListQuickActionsSharedAction(
R.drawable.ic_room_actions_open_at_bottom R.drawable.ic_room_actions_open_at_bottom
) )
data class OpenAnonymous(val roomId: String) : RoomListQuickActionsSharedAction(
R.string.room_list_quick_actions_open_anonymous,
R.drawable.ic_room_actions_open_anonymous
)
data class NotificationsAllNoisy(val roomId: String) : RoomListQuickActionsSharedAction( data class NotificationsAllNoisy(val roomId: String) : RoomListQuickActionsSharedAction(
R.string.room_list_quick_actions_notifications_all_noisy, R.string.room_list_quick_actions_notifications_all_noisy,
R.drawable.ic_room_actions_notifications_all_noisy R.drawable.ic_room_actions_notifications_all_noisy

View file

@ -149,13 +149,14 @@ class DefaultNavigator @Inject constructor(
eventId: String?, eventId: String?,
buildTask: Boolean, buildTask: Boolean,
isInviteAlreadyAccepted: Boolean, isInviteAlreadyAccepted: Boolean,
openAtFirstUnread: Boolean? openAtFirstUnread: Boolean?,
openAnonymously: Boolean
) { ) {
if (sessionHolder.getSafeActiveSession()?.getRoom(roomId) == null) { if (sessionHolder.getSafeActiveSession()?.getRoom(roomId) == null) {
fatalError("Trying to open an unknown room $roomId", vectorPreferences.failFast()) fatalError("Trying to open an unknown room $roomId", vectorPreferences.failFast())
return return
} }
val args = TimelineArgs(roomId = roomId, eventId = eventId, isInviteAlreadyAccepted = isInviteAlreadyAccepted, openAtFirstUnread = openAtFirstUnread) val args = TimelineArgs(roomId = roomId, eventId = eventId, isInviteAlreadyAccepted = isInviteAlreadyAccepted, openAtFirstUnread = openAtFirstUnread, openAnonymously = openAnonymously)
val intent = RoomDetailActivity.newIntent(context, args) val intent = RoomDetailActivity.newIntent(context, args)
startActivity(context, intent, buildTask) startActivity(context, intent, buildTask)
} }

View file

@ -50,7 +50,7 @@ interface Navigator {
fun softLogout(context: Context) fun softLogout(context: Context)
fun openRoom(context: Context, roomId: String, eventId: String? = null, buildTask: Boolean = false, isInviteAlreadyAccepted: Boolean = false, openAtFirstUnread: Boolean? = null) fun openRoom(context: Context, roomId: String, eventId: String? = null, buildTask: Boolean = false, isInviteAlreadyAccepted: Boolean = false, openAtFirstUnread: Boolean? = null, openAnonymously: Boolean = false)
sealed class PostSwitchSpaceAction { sealed class PostSwitchSpaceAction {
object None : PostSwitchSpaceAction() object None : PostSwitchSpaceAction()

View file

@ -218,6 +218,7 @@ class VectorPreferences @Inject constructor(private val context: Context, privat
private const val SETTINGS_NOTIF_ONLY_ALERT_ONCE = "SETTINGS_NOTIF_ONLY_ALERT_ONCE" private const val SETTINGS_NOTIF_ONLY_ALERT_ONCE = "SETTINGS_NOTIF_ONLY_ALERT_ONCE"
private const val SETTINGS_HIDE_CALL_BUTTONS = "SETTINGS_HIDE_CALL_BUTTONS" private const val SETTINGS_HIDE_CALL_BUTTONS = "SETTINGS_HIDE_CALL_BUTTONS"
private const val SETTINGS_READ_RECEIPT_FOLLOWS_READ_MARKER = "SETTINGS_READ_RECEIPT_FOLLOWS_READ_MARKER" private const val SETTINGS_READ_RECEIPT_FOLLOWS_READ_MARKER = "SETTINGS_READ_RECEIPT_FOLLOWS_READ_MARKER"
private const val SETTINGS_SHOW_OPEN_ANONYMOUS = "SETTINGS_SHOW_OPEN_ANONYMOUS"
private const val DID_ASK_TO_ENABLE_SESSION_PUSH = "DID_ASK_TO_ENABLE_SESSION_PUSH" private const val DID_ASK_TO_ENABLE_SESSION_PUSH = "DID_ASK_TO_ENABLE_SESSION_PUSH"
@ -1095,6 +1096,11 @@ class VectorPreferences @Inject constructor(private val context: Context, privat
return defaultPrefs.getBoolean(SETTINGS_READ_RECEIPT_FOLLOWS_READ_MARKER, false) return defaultPrefs.getBoolean(SETTINGS_READ_RECEIPT_FOLLOWS_READ_MARKER, false)
} }
// SC addition
fun showOpenAnonymous(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_SHOW_OPEN_ANONYMOUS, false)
}
/** /**
* I likely do more fresh installs of the app than anyone else, so a shortcut to change some of the default settings to * I likely do more fresh installs of the app than anyone else, so a shortcut to change some of the default settings to
* my preferred values can safe me some time * my preferred values can safe me some time
@ -1119,6 +1125,7 @@ class VectorPreferences @Inject constructor(private val context: Context, privat
.putBoolean(SETTINGS_AGGREGATE_UNREAD_COUNTS, false) .putBoolean(SETTINGS_AGGREGATE_UNREAD_COUNTS, false)
.putBoolean(SETTINGS_ENABLE_SPACE_PAGER, true) .putBoolean(SETTINGS_ENABLE_SPACE_PAGER, true)
.putBoolean(SETTINGS_READ_RECEIPT_FOLLOWS_READ_MARKER, true) .putBoolean(SETTINGS_READ_RECEIPT_FOLLOWS_READ_MARKER, true)
.putBoolean(SETTINGS_SHOW_OPEN_ANONYMOUS, true)
.apply() .apply()
} }

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M15.1,19.37l1,1.74c-0.96,0.44 -2.01,0.73 -3.1,0.84v-2.02C13.74,19.84 14.44,19.65 15.1,19.37zM4.07,13H2.05c0.11,1.1 0.4,2.14 0.84,3.1l1.74,-1C4.35,14.44 4.16,13.74 4.07,13zM15.1,4.63l1,-1.74C15.14,2.45 14.1,2.16 13,2.05v2.02C13.74,4.16 14.44,4.35 15.1,4.63zM19.93,11h2.02c-0.11,-1.1 -0.4,-2.14 -0.84,-3.1l-1.74,1C19.65,9.56 19.84,10.26 19.93,11zM8.9,19.37l-1,1.74c0.96,0.44 2.01,0.73 3.1,0.84v-2.02C10.26,19.84 9.56,19.65 8.9,19.37zM11,4.07V2.05c-1.1,0.11 -2.14,0.4 -3.1,0.84l1,1.74C9.56,4.35 10.26,4.16 11,4.07zM18.36,7.17l1.74,-1.01c-0.63,-0.87 -1.4,-1.64 -2.27,-2.27l-1.01,1.74C17.41,6.08 17.92,6.59 18.36,7.17zM4.63,8.9l-1.74,-1C2.45,8.86 2.16,9.9 2.05,11h2.02C4.16,10.26 4.35,9.56 4.63,8.9zM19.93,13c-0.09,0.74 -0.28,1.44 -0.56,2.1l1.74,1c0.44,-0.96 0.73,-2.01 0.84,-3.1H19.93zM16.83,18.36l1.01,1.74c0.87,-0.63 1.64,-1.4 2.27,-2.27l-1.74,-1.01C17.92,17.41 17.41,17.92 16.83,18.36zM7.17,5.64L6.17,3.89C5.29,4.53 4.53,5.29 3.9,6.17l1.74,1.01C6.08,6.59 6.59,6.08 7.17,5.64zM5.64,16.83L3.9,17.83c0.63,0.87 1.4,1.64 2.27,2.27l1.01,-1.74C6.59,17.92 6.08,17.41 5.64,16.83zM13,7h-2v5.41l4.29,4.29l1.41,-1.41L13,11.59V7z"/>
</vector>

View file

@ -186,4 +186,8 @@
<string name="settings_read_receipt_follows_read_marker">Only mark chats as read if fully read</string> <string name="settings_read_receipt_follows_read_marker">Only mark chats as read if fully read</string>
<string name="settings_read_receipt_follows_read_marker_summary">Do not update your read receipt when opening the room, but only gradually while reading</string> <string name="settings_read_receipt_follows_read_marker_summary">Do not update your read receipt when opening the room, but only gradually while reading</string>
<string name="room_list_quick_actions_open_anonymous">Open without reading</string>
<string name="settings_show_open_anonymous">Open without reading</string>
<string name="settings_show_open_anonymous_summary">Show option to open a room without automatically marking it read</string>
</resources> </resources>

View file

@ -66,6 +66,12 @@
android:title="@string/settings_enable_space_pager" android:title="@string/settings_enable_space_pager"
android:summary="@string/settings_enable_space_pager_summary" /> android:summary="@string/settings_enable_space_pager_summary" />
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_SHOW_OPEN_ANONYMOUS"
android:title="@string/settings_show_open_anonymous"
android:summary="@string/settings_show_open_anonymous_summary" />
</im.vector.app.core.preference.VectorPreferenceCategory> </im.vector.app.core.preference.VectorPreferenceCategory>