mirror of
https://github.com/tuskyapp/Tusky
synced 2025-01-03 04:19:24 +01:00
3532: Show badge on conversations tab on new conversations (#3890)
Fixes #3532 (Old PR, now closed: https://github.com/tuskyapp/Tusky/pull/3533) Listens on new notifications and if a "direct mention" is detected a badge (red dot) is shown on the conversations tab if present. I am missing things like this a lot and also big accounts are unhappy with the usability so far: https://mastodon.social/@pallenberg/110129889996182814
This commit is contained in:
parent
ff1c4a4b27
commit
b286255630
1016
app/schemas/com.keylesspalace.tusky.db.AppDatabase/54.json
Normal file
1016
app/schemas/com.keylesspalace.tusky.db.AppDatabase/54.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -67,8 +67,11 @@ import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.keylesspalace.tusky.appstore.AnnouncementReadEvent
|
||||
import com.keylesspalace.tusky.appstore.CacheUpdater
|
||||
import com.keylesspalace.tusky.appstore.ConversationsLoadingEvent
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.MainTabsChangedEvent
|
||||
import com.keylesspalace.tusky.appstore.NewNotificationsEvent
|
||||
import com.keylesspalace.tusky.appstore.NotificationsLoadingEvent
|
||||
import com.keylesspalace.tusky.appstore.ProfileEditedEvent
|
||||
import com.keylesspalace.tusky.components.account.AccountActivity
|
||||
import com.keylesspalace.tusky.components.accountlist.AccountListActivity
|
||||
@ -90,6 +93,7 @@ import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.db.DraftsAlert
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Notification
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.interfaces.AccountSelectionListener
|
||||
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
|
||||
import com.keylesspalace.tusky.interfaces.FabFragment
|
||||
@ -181,6 +185,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
||||
/** Adapter for the different timeline tabs */
|
||||
private lateinit var tabAdapter: MainPagerAdapter
|
||||
|
||||
private var directMessageTab: TabLayout.Tab? = null
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -324,11 +330,32 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
||||
|
||||
setupTabs(false)
|
||||
}
|
||||
|
||||
is AnnouncementReadEvent -> {
|
||||
unreadAnnouncementsCount--
|
||||
updateAnnouncementsBadge()
|
||||
}
|
||||
is NewNotificationsEvent -> {
|
||||
directMessageTab?.let { tab ->
|
||||
if (event.accountId == activeAccount.accountId) {
|
||||
val hasDirectMessageNotification =
|
||||
event.notifications.any { it.type == Notification.Type.MENTION && it.status?.visibility == Status.Visibility.DIRECT }
|
||||
|
||||
if (hasDirectMessageNotification) {
|
||||
showDirectMessageBadge(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is NotificationsLoadingEvent -> {
|
||||
if (event.accountId == activeAccount.accountId) {
|
||||
showDirectMessageBadge(false)
|
||||
}
|
||||
}
|
||||
is ConversationsLoadingEvent -> {
|
||||
if (event.accountId == activeAccount.accountId) {
|
||||
showDirectMessageBadge(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -374,6 +401,20 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
||||
draftsAlert.observeInContext(this, true)
|
||||
}
|
||||
|
||||
private fun showDirectMessageBadge(showBadge: Boolean) {
|
||||
directMessageTab?.let { tab ->
|
||||
tab.badge?.isVisible = showBadge
|
||||
|
||||
// TODO a bit cumbersome (also for resetting)
|
||||
accountManager.activeAccount?.let {
|
||||
if (it.hasDirectMessageBadge != showBadge) {
|
||||
it.hasDirectMessageBadge = showBadge
|
||||
accountManager.saveAccount(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.activity_main, menu)
|
||||
menu.findItem(R.id.action_search)?.apply {
|
||||
@ -770,6 +811,8 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
||||
// Detach any existing mediator before changing tab contents and attaching a new mediator
|
||||
tabLayoutMediator?.detach()
|
||||
|
||||
directMessageTab = null
|
||||
|
||||
tabAdapter.tabs = tabs
|
||||
tabAdapter.notifyItemRangeChanged(0, tabs.size)
|
||||
|
||||
@ -780,6 +823,11 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
||||
LIST -> tabs[position].arguments[1]
|
||||
else -> getString(tabs[position].text)
|
||||
}
|
||||
if (tabs[position].id == DIRECT) {
|
||||
tab.orCreateBadge
|
||||
tab.badge?.isVisible = accountManager.activeAccount?.hasDirectMessageBadge ?: false
|
||||
directMessageTab = tab
|
||||
}
|
||||
}.also { it.attach() }
|
||||
|
||||
// Selected tab is either
|
||||
@ -808,6 +856,17 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
||||
binding.mainToolbar.title = tab.contentDescription
|
||||
|
||||
refreshComposeButtonState(tabAdapter, tab.position)
|
||||
|
||||
if (tab == directMessageTab) {
|
||||
tab.badge?.isVisible = false
|
||||
|
||||
accountManager.activeAccount?.let {
|
||||
if (it.hasDirectMessageBadge) {
|
||||
it.hasDirectMessageBadge = false
|
||||
accountManager.saveAccount(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTabUnselected(tab: TabLayout.Tab) {}
|
||||
|
@ -2,6 +2,7 @@ package com.keylesspalace.tusky.appstore
|
||||
|
||||
import com.keylesspalace.tusky.TabData
|
||||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Notification
|
||||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
|
||||
@ -20,3 +21,6 @@ data class PollVoteEvent(val statusId: String, val poll: Poll) : Event
|
||||
data class DomainMuteEvent(val instance: String) : Event
|
||||
data class AnnouncementReadEvent(val announcementId: String) : Event
|
||||
data class FilterUpdatedEvent(val filterContext: List<String>) : Event
|
||||
data class NewNotificationsEvent(val accountId: String, val notifications: List<Notification>) : Event
|
||||
data class ConversationsLoadingEvent(val accountId: String) : Event
|
||||
data class NotificationsLoadingEvent(val accountId: String) : Event
|
||||
|
@ -40,6 +40,7 @@ import com.google.android.material.color.MaterialColors
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.StatusListActivity
|
||||
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder
|
||||
import com.keylesspalace.tusky.appstore.ConversationsLoadingEvent
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||
import com.keylesspalace.tusky.components.account.AccountActivity
|
||||
@ -54,6 +55,7 @@ import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.util.CardViewMode
|
||||
import com.keylesspalace.tusky.util.StatusDisplayOptions
|
||||
import com.keylesspalace.tusky.util.hide
|
||||
import com.keylesspalace.tusky.util.isAnyLoading
|
||||
import com.keylesspalace.tusky.util.show
|
||||
import com.keylesspalace.tusky.util.viewBinding
|
||||
import com.keylesspalace.tusky.viewdata.AttachmentViewData
|
||||
@ -64,6 +66,7 @@ import com.mikepenz.iconics.utils.sizeDp
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
@ -128,6 +131,12 @@ class ConversationsFragment :
|
||||
binding.statusView.hide()
|
||||
binding.progressBar.hide()
|
||||
|
||||
if (loadState.isAnyLoading()) {
|
||||
runBlocking {
|
||||
eventHub.dispatch(ConversationsLoadingEvent(accountManager.activeAccount?.accountId ?: ""))
|
||||
}
|
||||
}
|
||||
|
||||
if (adapter.itemCount == 0) {
|
||||
when (loadState.refresh) {
|
||||
is LoadState.NotLoading -> {
|
||||
|
@ -4,6 +4,8 @@ import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.NewNotificationsEvent
|
||||
import com.keylesspalace.tusky.components.notifications.NotificationHelper.filterNotification
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
@ -46,7 +48,8 @@ data class Links(val next: String?, val prev: String?) {
|
||||
class NotificationFetcher @Inject constructor(
|
||||
private val mastodonApi: MastodonApi,
|
||||
private val accountManager: AccountManager,
|
||||
private val context: Context
|
||||
private val context: Context,
|
||||
private val eventHub: EventHub
|
||||
) {
|
||||
suspend fun fetchAndShow() {
|
||||
for (account in accountManager.getAllAccountsOrderedByActive()) {
|
||||
@ -60,6 +63,10 @@ class NotificationFetcher @Inject constructor(
|
||||
.sortedWith(compareBy({ it.id.length }, { it.id })) // oldest notifications first
|
||||
.toMutableList()
|
||||
|
||||
// TODO do this before filter above? But one could argue that (for example) a tab badge is also a notification
|
||||
// (and should therefore adhere to the notification config).
|
||||
eventHub.dispatch(NewNotificationsEvent(account.accountId, notifications))
|
||||
|
||||
// There's a maximum limit on the number of notifications an Android app
|
||||
// can display. If the total number of notifications (current notifications,
|
||||
// plus new ones) exceeds this then some newer notifications will be dropped.
|
||||
|
@ -104,7 +104,10 @@ data class AccountEntity(
|
||||
|
||||
/** true if the connected Mastodon account is locked (has to manually approve all follow requests **/
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var locked: Boolean = false
|
||||
var locked: Boolean = false,
|
||||
|
||||
@ColumnInfo(defaultValue = "0")
|
||||
var hasDirectMessageBadge: Boolean = false
|
||||
) {
|
||||
|
||||
val identifier: String
|
||||
|
@ -42,12 +42,13 @@ import java.io.File;
|
||||
TimelineAccountEntity.class,
|
||||
ConversationEntity.class
|
||||
},
|
||||
version = 53,
|
||||
version = 54,
|
||||
autoMigrations = {
|
||||
@AutoMigration(from = 48, to = 49),
|
||||
@AutoMigration(from = 49, to = 50, spec = AppDatabase.MIGRATION_49_50.class),
|
||||
@AutoMigration(from = 50, to = 51),
|
||||
@AutoMigration(from = 51, to = 52),
|
||||
@AutoMigration(from = 53, to = 54) // hasDirectMessageBadge in AccountEntity
|
||||
}
|
||||
)
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
|
@ -33,6 +33,8 @@
|
||||
* see <http://www.gnu.org/licenses>. */
|
||||
package com.keylesspalace.tusky.util
|
||||
|
||||
import androidx.paging.CombinedLoadStates
|
||||
import androidx.paging.LoadState
|
||||
import com.keylesspalace.tusky.entity.Notification
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.entity.TrendingTag
|
||||
@ -87,3 +89,7 @@ fun List<TrendingTag>.toViewData(): List<TrendingViewData.Tag> {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun CombinedLoadStates.isAnyLoading(): Boolean {
|
||||
return this.refresh == LoadState.Loading || this.append == LoadState.Loading || this.prepend == LoadState.Loading
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user