mirror of
https://github.com/FluxaMedia/fluxa.git
synced 2026-08-20 05:59:00 +00:00
Move calendar business logic to fluxa-core via coreInvoke
Season-candidate selection, widget row formatting, and release notification filtering now call the equivalent Rust calendar_plan functions through core_invoke instead of duplicating that logic in Kotlin.
This commit is contained in:
parent
0fa3ef723e
commit
276c4fb766
3 changed files with 96 additions and 37 deletions
|
|
@ -12,7 +12,10 @@ import android.widget.RemoteViews
|
|||
import com.fluxa.app.R
|
||||
import com.fluxa.app.common.AppStrings
|
||||
import com.fluxa.app.common.locale
|
||||
import com.fluxa.app.core.rust.FluxaCoreUniFfi
|
||||
import com.fluxa.app.ui.MainActivity
|
||||
import com.google.gson.JsonArray as GsonArray
|
||||
import com.google.gson.JsonObject as GsonObject
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import java.text.SimpleDateFormat
|
||||
|
|
@ -67,12 +70,12 @@ class CalendarWidgetProvider : AppWidgetProvider() {
|
|||
accentColorArgb: Int = 0xFFFFFFFF.toInt()
|
||||
) {
|
||||
val rows = JSONArray().apply {
|
||||
items.take(MAX_ROWS).forEach { item ->
|
||||
fetchWidgetRows(items).forEach { row ->
|
||||
put(JSONObject().apply {
|
||||
put("date", formatWidgetDate(item.dateIso, language))
|
||||
put("title", item.title)
|
||||
put("subtitle", item.episodeTitle ?: item.subtitle.orEmpty())
|
||||
put("episode", widgetEpisodeText(item))
|
||||
put("date", formatWidgetDate(row.get("dateIso").asString, language))
|
||||
put("title", row.get("title").asString)
|
||||
put("subtitle", row.get("subtitle")?.asString.orEmpty())
|
||||
put("episode", row.get("episodeText")?.asString.orEmpty())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -160,10 +163,25 @@ class CalendarWidgetProvider : AppWidgetProvider() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun widgetEpisodeText(item: CalendarUpcomingItem): String {
|
||||
val season = item.seasonNumber
|
||||
val episode = item.episodeNumber
|
||||
return if (season != null && episode != null) "S$season:E$episode" else ""
|
||||
private fun fetchWidgetRows(items: List<CalendarUpcomingItem>): List<com.google.gson.JsonObject> {
|
||||
val requestItems = GsonArray().apply {
|
||||
items.forEach { item ->
|
||||
add(GsonObject().apply {
|
||||
addProperty("dateIso", item.dateIso)
|
||||
addProperty("title", item.title)
|
||||
item.subtitle?.let { addProperty("subtitle", it) }
|
||||
item.episodeTitle?.let { addProperty("episodeTitle", it) }
|
||||
item.seasonNumber?.let { addProperty("seasonNumber", it) }
|
||||
item.episodeNumber?.let { addProperty("episodeNumber", it) }
|
||||
})
|
||||
}
|
||||
}
|
||||
val request = GsonObject().apply {
|
||||
add("items", requestItems)
|
||||
addProperty("maxRows", MAX_ROWS)
|
||||
}
|
||||
val value = FluxaCoreUniFfi.coreInvokeValue("calendarWidgetRows", request.toString())
|
||||
return value.asJsonArray.map { it.asJsonObject }
|
||||
}
|
||||
|
||||
private fun formatWidgetDate(dateIso: String, language: String): String {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.fluxa.app.ui.catalog
|
|||
|
||||
import com.fluxa.app.common.AppStrings
|
||||
import com.fluxa.app.common.ReleaseDateUtils
|
||||
import com.fluxa.app.core.rust.FluxaCoreUniFfi
|
||||
import com.fluxa.app.data.local.*
|
||||
import com.fluxa.app.data.local.UserProfile
|
||||
import com.fluxa.app.data.local.WatchlistManager
|
||||
|
|
@ -9,6 +10,9 @@ import com.fluxa.app.data.remote.Meta
|
|||
import com.fluxa.app.data.remote.MetaDetail
|
||||
import com.fluxa.app.data.remote.Video
|
||||
import com.fluxa.app.data.repository.StremioRepository
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
|
|
@ -27,6 +31,8 @@ internal class EpisodeCalendarLoader(
|
|||
private val repository: StremioRepository,
|
||||
private val watchlistManager: WatchlistManager
|
||||
) {
|
||||
private val gson = Gson()
|
||||
|
||||
suspend fun loadMonth(
|
||||
profile: UserProfile?,
|
||||
year: Int,
|
||||
|
|
@ -154,18 +160,12 @@ internal class EpisodeCalendarLoader(
|
|||
}
|
||||
|
||||
private fun calendarSeasonCandidates(meta: Meta, detail: MetaDetail): List<Int> {
|
||||
val seasonsCount = detail.seasonsCount ?: meta.seasonsCount ?: 1
|
||||
val watchedSeason = meta.lastVideoId
|
||||
?.split(":")
|
||||
?.getOrNull(1)
|
||||
?.toIntOrNull()
|
||||
val focused = listOfNotNull(
|
||||
watchedSeason,
|
||||
watchedSeason?.plus(1),
|
||||
seasonsCount
|
||||
).filter { it > 0 && it <= seasonsCount }
|
||||
val full = if (seasonsCount <= 8) (1..seasonsCount).toList() else focused
|
||||
return (focused + full).distinct().take(12)
|
||||
val request = JsonObject().apply {
|
||||
addProperty("seasonsCount", detail.seasonsCount ?: meta.seasonsCount ?: 1)
|
||||
meta.lastVideoId?.let { addProperty("lastVideoId", it) }
|
||||
}
|
||||
val value = FluxaCoreUniFfi.coreInvokeValue("calendarSeasonCandidates", request.toString())
|
||||
return gson.fromJson(value, object : TypeToken<List<Int>>() {}.type)
|
||||
}
|
||||
|
||||
private fun UserProfile.hasExternalContinueProvider(): Boolean {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.fluxa.app.ui.catalog
|
||||
|
||||
import com.fluxa.app.common.AppStrings
|
||||
import com.fluxa.app.core.rust.FluxaCoreUniFfi
|
||||
import com.fluxa.app.data.local.*
|
||||
import com.fluxa.app.data.remote.*
|
||||
import com.fluxa.app.data.repository.*
|
||||
|
|
@ -20,6 +21,8 @@ import androidx.core.app.NotificationManagerCompat
|
|||
import androidx.core.content.ContextCompat
|
||||
import com.fluxa.app.R
|
||||
import com.fluxa.app.data.remote.StremioService
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Request
|
||||
|
|
@ -30,15 +33,16 @@ object EpisodeNotificationHelper {
|
|||
private const val NOTIFIED_KEYS = "notified_keys"
|
||||
|
||||
suspend fun notifyReleasedEpisodes(context: Context, profile: UserProfile?, items: List<CalendarUpcomingItem>, todayIso: String) {
|
||||
if (profile?.safeNotificationsEnabled == false || profile?.safeAlertNewEpisodes == false) return
|
||||
val releasedToday = items.filter { it.dateIso == todayIso && it.meta.type == "series" }
|
||||
if (releasedToday.isEmpty()) return
|
||||
|
||||
val appContext = context.applicationContext
|
||||
val prefs = appContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
val alreadyNotified = prefs.getStringSet(NOTIFIED_KEYS, emptySet()).orEmpty()
|
||||
|
||||
val content = fetchNotificationContent(items, todayIso, alreadyNotified, profile)
|
||||
val releasedItems = content.getAsJsonArray("items")
|
||||
if (releasedItems.isEmpty) return
|
||||
|
||||
if (!canPostNotifications(appContext)) return
|
||||
ensureChannel(appContext)
|
||||
val prefs = appContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
val notified = prefs.getStringSet(NOTIFIED_KEYS, emptySet()).orEmpty().toMutableSet()
|
||||
val launchIntent = appContext.packageManager.getLaunchIntentForPackage(appContext.packageName)
|
||||
val pendingIntent = launchIntent?.let {
|
||||
PendingIntent.getActivity(
|
||||
|
|
@ -49,15 +53,18 @@ object EpisodeNotificationHelper {
|
|||
)
|
||||
}
|
||||
|
||||
releasedToday.forEach { item ->
|
||||
val key = "${profile?.id.orEmpty()}:${item.dateIso}:${item.meta.id}:${item.subtitle.orEmpty()}"
|
||||
if (!notified.add(key)) return@forEach
|
||||
val image = loadBitmap(item.artworkUrl())
|
||||
val notificationTitle = AppStrings.t(
|
||||
profile?.safeLanguage,
|
||||
if (item.episodeNumber == 1) "notification.new_season_released" else "notification.new_episode_released"
|
||||
)
|
||||
val text = releaseNotificationText(item, profile?.safeLanguage)
|
||||
releasedItems.forEach { element ->
|
||||
val row = element.asJsonObject
|
||||
val key = row.get("key").asString
|
||||
val original = items.firstOrNull {
|
||||
it.meta.id == row.get("metaId").asString &&
|
||||
it.dateIso == row.get("dateIso").asString &&
|
||||
it.seasonNumber == row.get("seasonNumber")?.takeIf { v -> !v.isJsonNull }?.asInt &&
|
||||
it.episodeNumber == row.get("episodeNumber")?.takeIf { v -> !v.isJsonNull }?.asInt
|
||||
} ?: return@forEach
|
||||
val image = loadBitmap(original.artworkUrl())
|
||||
val notificationTitle = AppStrings.t(profile?.safeLanguage, row.get("titleKey").asString)
|
||||
val text = releaseNotificationText(original, profile?.safeLanguage)
|
||||
val notification = NotificationCompat.Builder(appContext, CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||
.setContentTitle(notificationTitle)
|
||||
|
|
@ -79,7 +86,41 @@ object EpisodeNotificationHelper {
|
|||
.build()
|
||||
postNotification(appContext, key.hashCode(), notification)
|
||||
}
|
||||
prefs.edit().putStringSet(NOTIFIED_KEYS, notified).apply()
|
||||
|
||||
val newKeys = content.getAsJsonArray("keys").map { it.asString }
|
||||
prefs.edit().putStringSet(NOTIFIED_KEYS, alreadyNotified + newKeys).apply()
|
||||
}
|
||||
|
||||
private fun fetchNotificationContent(
|
||||
items: List<CalendarUpcomingItem>,
|
||||
todayIso: String,
|
||||
alreadyNotified: Set<String>,
|
||||
profile: UserProfile?
|
||||
): JsonObject {
|
||||
val requestItems = JsonArray().apply {
|
||||
items.forEach { item ->
|
||||
add(JsonObject().apply {
|
||||
addProperty("dateIso", item.dateIso)
|
||||
addProperty("metaId", item.meta.id)
|
||||
addProperty("metaType", item.meta.type)
|
||||
addProperty("title", item.title)
|
||||
item.subtitle?.let { addProperty("subtitle", it) }
|
||||
item.seasonNumber?.let { addProperty("seasonNumber", it) }
|
||||
item.episodeNumber?.let { addProperty("episodeNumber", it) }
|
||||
item.episodeTitle?.let { addProperty("episodeTitle", it) }
|
||||
item.artworkUrl()?.let { addProperty("artworkUrl", it) }
|
||||
})
|
||||
}
|
||||
}
|
||||
val request = JsonObject().apply {
|
||||
add("items", requestItems)
|
||||
addProperty("todayIso", todayIso)
|
||||
add("alreadyNotifiedKeys", JsonArray().apply { alreadyNotified.forEach { add(it) } })
|
||||
profile?.id?.let { addProperty("profileId", it) }
|
||||
addProperty("notificationsEnabled", profile?.safeNotificationsEnabled != false)
|
||||
addProperty("alertNewEpisodes", profile?.safeAlertNewEpisodes != false)
|
||||
}
|
||||
return FluxaCoreUniFfi.coreInvokeValue("calendarNotificationContent", request.toString()).asJsonObject
|
||||
}
|
||||
|
||||
private fun releaseNotificationText(item: CalendarUpcomingItem, language: String?): String {
|
||||
|
|
|
|||
Loading…
Reference in a new issue