diff --git a/app/src/main/java/dev/dimension/flare/common/ComposeInAppNotification.kt b/app/src/main/java/dev/dimension/flare/common/ComposeInAppNotification.kt
index 6c1a82ece..6e475464e 100644
--- a/app/src/main/java/dev/dimension/flare/common/ComposeInAppNotification.kt
+++ b/app/src/main/java/dev/dimension/flare/common/ComposeInAppNotification.kt
@@ -34,21 +34,20 @@ internal class ComposeInAppNotification : InAppNotification {
}
override fun onSuccess(message: Message) {
- val messageId =
- when (message) {
- Message.Compose -> R.string.compose_notification_success_title
- }
- _source.value = Event(Notification.StringNotification(messageId, success = true))
+ _source.value = Event(Notification.StringNotification(message.title, success = true))
}
override fun onError(
message: Message,
throwable: Throwable,
) {
- val messageId =
- when (message) {
- Message.Compose -> R.string.compose_notification_error_title
- }
- _source.value = Event(Notification.StringNotification(messageId, success = false))
+ _source.value = Event(Notification.StringNotification(message.title, success = false))
}
}
+
+private val Message.title
+ get() =
+ when (this) {
+ Message.Compose -> R.string.compose_notification_title
+ Message.LoginExpired -> R.string.notification_login_expired
+ }
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 585348353..6124feadb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -671,4 +671,6 @@
Find %1$s in %2$s using %3$s
+ Login expired, please login again
+
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/common/InAppNotification.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/common/InAppNotification.kt
index b17c2621d..b25b4cdfe 100644
--- a/shared/src/commonMain/kotlin/dev/dimension/flare/common/InAppNotification.kt
+++ b/shared/src/commonMain/kotlin/dev/dimension/flare/common/InAppNotification.kt
@@ -17,4 +17,5 @@ interface InAppNotification {
enum class Message {
Compose,
+ LoginExpired,
}
diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/CommentPagingSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/CommentPagingSource.kt
index d744b4695..54ee35770 100644
--- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/CommentPagingSource.kt
+++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/CommentPagingSource.kt
@@ -13,6 +13,7 @@ internal class CommentPagingSource(
private val service: VVOService,
private val event: StatusEvent.VVO,
private val accountKey: MicroBlogKey,
+ private val onClearMarker: () -> Unit,
) : PagingSource() {
override suspend fun load(params: LoadParams): LoadResult {
return try {
@@ -22,6 +23,9 @@ internal class CommentPagingSource(
LoginExpiredException,
)
}
+ if (params.key == null) {
+ onClearMarker.invoke()
+ }
val response =
service.getComments(
page = params.key ?: 1,
diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/HomeTimelineRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/HomeTimelineRemoteMediator.kt
index fbcecf46c..3843c92f0 100644
--- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/HomeTimelineRemoteMediator.kt
+++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/HomeTimelineRemoteMediator.kt
@@ -4,6 +4,8 @@ import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
+import dev.dimension.flare.common.InAppNotification
+import dev.dimension.flare.common.Message
import dev.dimension.flare.data.database.cache.CacheDatabase
import dev.dimension.flare.data.database.cache.mapper.VVO
import dev.dimension.flare.data.database.cache.model.DbPagingTimelineView
@@ -17,6 +19,7 @@ internal class HomeTimelineRemoteMediator(
private val database: CacheDatabase,
private val accountKey: MicroBlogKey,
private val pagingKey: String,
+ private val inAppNotification: InAppNotification,
) : RemoteMediator() {
override suspend fun initialize(): InitializeAction = InitializeAction.SKIP_INITIAL_REFRESH
@@ -27,6 +30,10 @@ internal class HomeTimelineRemoteMediator(
return try {
val config = service.config()
if (config.data?.login != true) {
+ inAppNotification.onError(
+ Message.LoginExpired,
+ LoginExpiredException,
+ )
return MediatorResult.Error(
LoginExpiredException,
)
diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/LikePagingSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/LikePagingSource.kt
index 3e9df7062..45b9699fc 100644
--- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/LikePagingSource.kt
+++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/LikePagingSource.kt
@@ -13,6 +13,7 @@ internal class LikePagingSource(
private val service: VVOService,
private val event: StatusEvent.VVO,
private val accountKey: MicroBlogKey,
+ private val onClearMarker: () -> Unit,
) : PagingSource() {
override suspend fun load(params: LoadParams): LoadResult {
return try {
@@ -22,6 +23,9 @@ internal class LikePagingSource(
LoginExpiredException,
)
}
+ if (params.key == null) {
+ onClearMarker.invoke()
+ }
val response =
service.getAttitudes(
page = params.key ?: 1,
diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/MentionRemoteMediator.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/MentionRemoteMediator.kt
index a2d1ad5de..fb76c0c00 100644
--- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/MentionRemoteMediator.kt
+++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/MentionRemoteMediator.kt
@@ -17,6 +17,7 @@ internal class MentionRemoteMediator(
private val database: CacheDatabase,
private val accountKey: MicroBlogKey,
private val pagingKey: String,
+ private val onClearMarker: () -> Unit,
) : RemoteMediator() {
var page = 1
@@ -40,6 +41,7 @@ internal class MentionRemoteMediator(
page = page,
).also {
database.pagingTimelineDao().delete(pagingKey = pagingKey, accountKey = accountKey)
+ onClearMarker.invoke()
}
}
diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/VVODataSource.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/VVODataSource.kt
index 8ca8d42d6..4d21d1c71 100644
--- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/VVODataSource.kt
+++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/datasource/vvo/VVODataSource.kt
@@ -8,6 +8,7 @@ import androidx.paging.cachedIn
import dev.dimension.flare.common.CacheData
import dev.dimension.flare.common.Cacheable
import dev.dimension.flare.common.FileItem
+import dev.dimension.flare.common.InAppNotification
import dev.dimension.flare.common.MemCacheable
import dev.dimension.flare.common.decodeJson
import dev.dimension.flare.data.database.cache.CacheDatabase
@@ -41,8 +42,10 @@ import dev.dimension.flare.ui.model.toUi
import dev.dimension.flare.ui.presenter.compose.ComposeStatus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.launch
+import kotlinx.datetime.Clock
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@@ -56,6 +59,7 @@ class VVODataSource(
private val database: CacheDatabase by inject()
private val localFilterRepository: LocalFilterRepository by inject()
private val coroutineScope: CoroutineScope by inject()
+ private val inAppNotification: InAppNotification by inject()
private val service by lazy {
VVOService(credential.chocolate)
}
@@ -78,6 +82,7 @@ class VVODataSource(
database,
accountKey,
pagingKey,
+ inAppNotification,
),
)
@@ -103,6 +108,9 @@ class VVODataSource(
database,
accountKey,
pagingKey,
+ onClearMarker = {
+ MemCacheable.update(notificationMarkerMentionKey, 0)
+ },
),
)
@@ -114,6 +122,9 @@ class VVODataSource(
service = service,
accountKey = accountKey,
event = this,
+ onClearMarker = {
+ MemCacheable.update(notificationMarkerCommentKey, 0)
+ },
)
}.flow.cachedIn(scope)
@@ -125,6 +136,9 @@ class VVODataSource(
service = service,
accountKey = accountKey,
event = this,
+ onClearMarker = {
+ MemCacheable.update(notificationMarkerLikeKey, 0)
+ },
)
}.flow.cachedIn(scope)
}
@@ -715,4 +729,42 @@ class VVODataSource(
}
}
}
+
+ private val notificationMarkerMentionKey: String
+ get() = "notificationBadgeCount_mention_$accountKey"
+
+ private val notificationMarkerCommentKey: String
+ get() = "notificationBadgeCount_comment_$accountKey"
+
+ private val notificationMarkerLikeKey: String
+ get() = "notificationBadgeCount_like_$accountKey"
+
+ override fun notificationBadgeCount(): CacheData =
+ Cacheable(
+ fetchSource = {
+ val config = service.config()
+ val st = config.data?.st
+ requireNotNull(st) { "st is null" }
+ val response =
+ service.remindUnread(
+ time = Clock.System.now().toEpochMilliseconds() / 1000,
+ st = st,
+ )
+ val mention = response.data?.mentionStatus ?: 0
+ val comment = response.data?.cmt ?: 0
+ val like = response.data?.attitude ?: 0
+
+ MemCacheable.update(notificationMarkerMentionKey, mention)
+ MemCacheable.update(notificationMarkerCommentKey, comment)
+ MemCacheable.update(notificationMarkerLikeKey, like)
+ },
+ cacheSource = {
+ val mentionFlow = MemCacheable.subscribe(notificationMarkerMentionKey)
+ val commentFlow = MemCacheable.subscribe(notificationMarkerCommentKey)
+ val likeFlow = MemCacheable.subscribe(notificationMarkerLikeKey)
+ combine(mentionFlow, commentFlow, likeFlow) { mention, comment, like ->
+ (mention + comment + like).toInt()
+ }
+ },
+ )
}
diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/NodeInfoService.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/NodeInfoService.kt
index 7ae5abe86..d550e7d54 100644
--- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/NodeInfoService.kt
+++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/nodeinfo/NodeInfoService.kt
@@ -10,6 +10,7 @@ import dev.dimension.flare.data.network.nodeinfo.model.Schema21
import dev.dimension.flare.model.PlatformType
import dev.dimension.flare.model.vvo
import dev.dimension.flare.model.vvoHost
+import dev.dimension.flare.model.vvoHostLong
import dev.dimension.flare.model.vvoHostShort
import dev.dimension.flare.model.xqtHost
import io.ktor.client.call.body
@@ -79,7 +80,7 @@ internal data object NodeInfoService {
if (host.equals(xqtHost, ignoreCase = true) || host.equals("x.social", ignoreCase = true)) {
return@coroutineScope PlatformType.xQt
}
- val vvo = listOf(vvoHost, vvo, vvoHostShort, "vvo.social")
+ val vvo = listOf(vvoHost, vvo, vvoHostShort, "vvo.social", vvoHostLong)
if (vvo.any { it.equals(host, ignoreCase = true) }) {
return@coroutineScope PlatformType.VVo
}
diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/vvo/api/ConfigApi.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/vvo/api/ConfigApi.kt
index 075133a54..14dfbb88b 100644
--- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/vvo/api/ConfigApi.kt
+++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/vvo/api/ConfigApi.kt
@@ -1,10 +1,19 @@
package dev.dimension.flare.data.network.vvo.api
import de.jensklingenberg.ktorfit.http.GET
+import de.jensklingenberg.ktorfit.http.Header
+import de.jensklingenberg.ktorfit.http.Query
import dev.dimension.flare.data.network.vvo.model.Config
+import dev.dimension.flare.data.network.vvo.model.UnreadData
import dev.dimension.flare.data.network.vvo.model.VVOResponse
internal interface ConfigApi {
@GET("api/config")
suspend fun config(): VVOResponse
+
+ @GET("api/remind/unread")
+ suspend fun remindUnread(
+ @Query("t") time: Long,
+ @Header("X-Xsrf-Token") st: String,
+ ): VVOResponse
}
diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/vvo/model/TimelineData.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/vvo/model/TimelineData.kt
index 737a09cf9..ca8e21409 100644
--- a/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/vvo/model/TimelineData.kt
+++ b/shared/src/commonMain/kotlin/dev/dimension/flare/data/network/vvo/model/TimelineData.kt
@@ -564,3 +564,25 @@ data class UploadResponse(
@SerialName("original_pic")
val originalPic: String? = null,
)
+
+@Serializable
+data class UnreadData(
+ val cmt: Long? = null,
+ val status: Long? = null,
+ val follower: Long? = null,
+ val dm: Long? = null,
+ @SerialName("mention_cmt")
+ val mentionCmt: Long? = null,
+ @SerialName("mention_status")
+ val mentionStatus: Long? = null,
+ val attitude: Long? = null,
+ val unreadmblog: Long? = null,
+ val uid: String? = null,
+ val bi: Long? = null,
+ val newfans: Long? = null,
+ val unreadmsg: Map? = null,
+// val group: Any? = null,
+ val notice: Long? = null,
+ val photo: Long? = null,
+ val msgbox: Long? = null,
+)
diff --git a/shared/src/commonMain/kotlin/dev/dimension/flare/model/PlatformType.kt b/shared/src/commonMain/kotlin/dev/dimension/flare/model/PlatformType.kt
index 0323422ab..ff3dbbb5b 100644
--- a/shared/src/commonMain/kotlin/dev/dimension/flare/model/PlatformType.kt
+++ b/shared/src/commonMain/kotlin/dev/dimension/flare/model/PlatformType.kt
@@ -59,3 +59,9 @@ val vvoHostShort: String =
append(vvo)
append("LmNu".decodeBase64String())
}
+
+val vvoHostLong: String =
+ buildString {
+ append("d2Vp".decodeBase64String())
+ append("Ym8uY29t".decodeBase64String())
+ }