Compare commits

...

8 Commits

Author SHA1 Message Date
semantic-release-bot
e115e8cd90 chore: Release v1.26.0-dev.32 [skip ci]
# app [1.26.0-dev.32](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.31...v1.26.0-dev.32) (2026-03-08)

### Features

* migrate to API v5 ([2270f32](2270f329de))
2026-03-08 20:23:49 +00:00
Ax333l
2270f329de feat: migrate to API v5 2026-03-08 21:16:49 +01:00
semantic-release-bot
55f0cdebd5 chore: Release v1.26.0-dev.31 [skip ci]
# app [1.26.0-dev.31](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.30...v1.26.0-dev.31) (2026-03-04)

### Features

* Don't mark all announcements as read on first launch, and set the correct default tags ([398ca00](398ca006b6))
2026-03-04 03:54:17 +00:00
Ushie
398ca006b6 feat: Don't mark all announcements as read on first launch, and set the correct default tags 2026-03-04 06:46:40 +03:00
semantic-release-bot
d368c0ff6f chore: Release v1.26.0-dev.30 [skip ci]
# app [1.26.0-dev.30](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.29...v1.26.0-dev.30) (2026-03-02)

### Features

* Update AAPT2 to built-tools v35. ([1ba11b3](1ba11b3021))
2026-03-02 17:24:20 +00:00
oSumAtrIX
1ba11b3021 feat: Update AAPT2 to built-tools v35.
The binaries provenance is attested by GitHub at https://github.com/ReVanced/aapt2/attestations.
The artifacts are available at https://github.com/ReVanced/aapt2/releases/tag/v1.0.0.
2026-03-02 18:16:54 +01:00
oSumAtrIX
d29e736c4a ci: Do not archive PR build artifacts 2026-03-02 12:57:53 +01:00
semantic-release-bot
896d8cc874 chore: Release v1.26.0-dev.29 [skip ci]
# app [1.26.0-dev.29](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.28...v1.26.0-dev.29) (2026-02-28)

### Bug Fixes

* Exclude desktop AAPT and disable R8 optimizations ([6ce5b67](6ce5b672fe))
* unexclude other destabilizers ([2c3d436](2c3d436b25))
2026-02-28 14:01:21 +00:00
14 changed files with 69 additions and 73 deletions

View File

@@ -29,3 +29,4 @@ jobs:
path: |
app/build/outputs/apk/release/revanced-manager*.apk
app/build/outputs/apk/release/revanced-manager*.apk.asc
archive: false

View File

@@ -1,3 +1,32 @@
# app [1.26.0-dev.32](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.31...v1.26.0-dev.32) (2026-03-08)
### Features
* migrate to API v5 ([2270f32](https://github.com/ReVanced/revanced-manager/commit/2270f329de25376eb9f0ce196994cc841977ff2a))
# app [1.26.0-dev.31](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.30...v1.26.0-dev.31) (2026-03-04)
### Features
* Don't mark all announcements as read on first launch, and set the correct default tags ([398ca00](https://github.com/ReVanced/revanced-manager/commit/398ca006b637048e6280edaf9685800f7638e6ba))
# app [1.26.0-dev.30](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.29...v1.26.0-dev.30) (2026-03-02)
### Features
* Update AAPT2 to built-tools v35. ([1ba11b3](https://github.com/ReVanced/revanced-manager/commit/1ba11b30218be263a56f6512b968f5e3dded9daf))
# app [1.26.0-dev.29](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.28...v1.26.0-dev.29) (2026-02-28)
### Bug Fixes
* Exclude desktop AAPT and disable R8 optimizations ([6ce5b67](https://github.com/ReVanced/revanced-manager/commit/6ce5b672feb677eccafffb6a04bdf5513f6d62fa))
* unexclude other destabilizers ([2c3d436](https://github.com/ReVanced/revanced-manager/commit/2c3d436b258f100feea0b9864b7131ae2c5bda79))
# app [1.26.0-dev.28](https://github.com/ReVanced/revanced-manager/compare/v1.26.0-dev.27...v1.26.0-dev.28) (2026-02-28)

View File

@@ -1 +1 @@
version = 1.26.0-dev.28
version = 1.26.0-dev.32

View File

@@ -2,7 +2,6 @@ package app.revanced.manager.domain.repository
import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.network.dto.ReVancedAnnouncement
import app.revanced.manager.network.dto.ReVancedAnnouncementTag
import app.revanced.manager.network.utils.getOrNull
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -12,7 +11,6 @@ class AnnouncementRepository(
) {
private val mutex = Mutex()
private var cachedAnnouncements: List<ReVancedAnnouncement>? = null
private var cachedTags: List<ReVancedAnnouncementTag>? = null
suspend fun getAnnouncements(forceRefresh: Boolean = false): List<ReVancedAnnouncement>? {
mutex.withLock {
@@ -22,13 +20,4 @@ class AnnouncementRepository(
return cachedAnnouncements
}
}
suspend fun getTags(forceRefresh: Boolean = false): List<ReVancedAnnouncementTag>? {
mutex.withLock {
if (cachedTags == null || forceRefresh) {
cachedTags = api.getAnnouncementTags().getOrNull()
}
return cachedTags
}
}
}

View File

@@ -2,8 +2,8 @@ package app.revanced.manager.network.api
import app.revanced.manager.BuildConfig
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.manager.base.Preference
import app.revanced.manager.network.dto.ReVancedAnnouncement
import app.revanced.manager.network.dto.ReVancedAnnouncementTag
import app.revanced.manager.network.dto.ReVancedAsset
import app.revanced.manager.network.dto.ReVancedGitRepository
import app.revanced.manager.network.dto.ReVancedInfo
@@ -25,7 +25,7 @@ class ReVancedAPI(
Dispatchers.IO
) {
client.request {
url("$api/v4/$route")
url("$api/v5/$route")
}
}
@@ -33,17 +33,19 @@ class ReVancedAPI(
suspend fun getAnnouncements() = request<List<ReVancedAnnouncement>>("announcements")
suspend fun getAnnouncementTags() = request<List<ReVancedAnnouncementTag>>("announcements/tags")
suspend fun getAppUpdate() =
getLatestAppInfo().getOrThrow().takeIf { it.version.removePrefix("v") != BuildConfig.VERSION_NAME }
suspend fun getLatestAppInfo() =
request<ReVancedAsset>("manager?prerelease=${prefs.useManagerPrereleases.get()}")
request<ReVancedAsset>("manager${prefs.useManagerPrereleases.prereleaseString()}")
suspend fun getPatchesUpdate() = request<ReVancedAsset>("patches?prerelease=${prefs.usePatchesPrereleases.get()}")
suspend fun getPatchesUpdate() = request<ReVancedAsset>("patches${prefs.usePatchesPrereleases.prereleaseString()}")
suspend fun getContributors() = request<List<ReVancedGitRepository>>("contributors")
suspend fun getInfo(api: String? = null) = request<ReVancedInfo>(api ?: apiUrl(), "about")
private companion object {
suspend fun Preference<Boolean>.prereleaseString() = if (get()) "/prerelease" else ""
}
}

View File

@@ -2,9 +2,12 @@ package app.revanced.manager.network.dto
import android.os.Parcelable
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlin.time.Clock
@Parcelize
@Serializable
@@ -14,10 +17,11 @@ data class ReVancedAnnouncement(
val title: String,
val content: String,
val tags: List<String>,
val attachments: List<String>,
@SerialName("created_at")
val createdAt: LocalDateTime,
@SerialName("archived_at")
val archivedAt: LocalDateTime,
val archivedAt: LocalDateTime?,
val level: Int,
) : Parcelable
) : Parcelable {
val isArchived get() = archivedAt?.toInstant(TimeZone.UTC)?.let { it < Clock.System.now() } ?: false
}

View File

@@ -1,8 +0,0 @@
package app.revanced.manager.network.dto
import kotlinx.serialization.Serializable
@Serializable
data class ReVancedAnnouncementTag(
val name: String
)

View File

@@ -50,10 +50,7 @@ import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.viewmodel.AnnouncementsViewModel
import app.revanced.manager.util.relativeTime
import app.revanced.manager.util.transparentListItemColors
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import org.koin.androidx.compose.koinViewModel
import kotlin.time.Clock
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -64,7 +61,7 @@ fun AnnouncementsScreen(
) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
var showFilterSheet by rememberSaveable { mutableStateOf(false) }
val tags by vm.tags.collectAsStateWithLifecycle()
val tags by vm.tags.collectAsStateWithLifecycle(null)
val selectedTags by vm.selectedTags.getAsState()
val showArchived by vm.showArchived.collectAsStateWithLifecycle()
val announcements by vm.announcements.collectAsStateWithLifecycle(emptyList())
@@ -129,6 +126,7 @@ fun AnnouncementsScreen(
modifier = Modifier.fillMaxWidth()
)
}
AnnouncementCard(
modifier = Modifier
.fillMaxWidth(),
@@ -141,7 +139,7 @@ fun AnnouncementsScreen(
author = announcement.author,
content = announcement.content,
unread = announcement.id !in readAnnouncements,
archived = announcement.archivedAt.toInstant(TimeZone.UTC) < Clock.System.now()
archived = announcement.isArchived
)
}
}
@@ -161,7 +159,7 @@ fun AnnouncementsScreen(
@Composable
private fun FilterBottomSheet(
onDismissRequest: () -> Unit,
tags: List<String>,
tags: Set<String>,
selectedTags: Set<String>,
showArchived: Boolean,
onShowArchivedChange: (Boolean) -> Unit,

View File

@@ -6,15 +6,12 @@ import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.AnnouncementRepository
import app.revanced.manager.network.dto.ReVancedAnnouncement
import kotlin.time.Clock
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
class AnnouncementsViewModel(
private val announcementRepository: AnnouncementRepository,
@@ -23,37 +20,26 @@ class AnnouncementsViewModel(
) : ViewModel() {
private val allAnnouncements = MutableStateFlow<List<ReVancedAnnouncement>?>(null)
private val _tags = MutableStateFlow<List<String>?>(null)
val tags get() = _tags.asStateFlow()
val tags = allAnnouncements.map { it?.tags }
val selectedTags = preferences.selectedAnnouncementTags
val readAnnouncements = preferences.readAnnouncements
val showArchived = MutableStateFlow(false)
val announcements = combine(
allAnnouncements,
_tags,
selectedTags.flow,
showArchived
) { source, tags, selectedTags, showArchived ->
) { source, selectedTags, showArchived ->
if (source == null) return@combine null
// Only filter by tags that actually exist
val availableTags = tags.orEmpty().toSet()
val availableTags = source.tags
val validSelected = selectedTags.intersect(availableTags)
val visibleAnnouncements = if (showArchived) {
source
} else {
source.filter { announcement ->
announcement.archivedAt.toInstant(TimeZone.UTC) > Clock.System.now()
}
}
source.filter { announcement ->
if (!showArchived && announcement.isArchived) return@filter false
if (validSelected.isEmpty()) {
visibleAnnouncements
} else {
visibleAnnouncements.filter { announcement ->
announcement.tags.any(validSelected::contains)
}
if (!validSelected.isEmpty()) announcement.tags.any(validSelected::contains)
else true
}
}
@@ -84,7 +70,6 @@ class AnnouncementsViewModel(
viewModelScope.launch {
if (!network.isConnected()) {
allAnnouncements.value = emptyList()
_tags.value = emptyList()
return@launch
}
@@ -92,10 +77,14 @@ class AnnouncementsViewModel(
announcementRepository.getAnnouncements()?.let {
allAnnouncements.value = it
}
announcementRepository.getTags()?.let {
_tags.value = it.map { tag -> tag.name }
}
}
}
}
private companion object {
val List<ReVancedAnnouncement>.tags: Set<String>
get() = flatMapTo(
mutableSetOf()
) { it.tags }
}
}

View File

@@ -98,22 +98,14 @@ class DashboardViewModel(
} ?: throw IllegalStateException("Announcements could not be retrieved")
val readAnnouncements = prefs.readAnnouncements.get()
if (readAnnouncements.isEmpty()) {
val announcementIds = announcements.mapTo(mutableSetOf()) { it.id }
prefs.readAnnouncements.update(announcementIds)
return@uiSafe
}
unreadAnnouncement = announcements.firstOrNull { announcement ->
val isNotArchived =
announcement.archivedAt.toInstant(TimeZone.UTC) > Clock.System.now()
val hasRelevantTag = "revanced" in announcement.tags ||
"manager" in announcement.tags
val hasRelevantTag = announcement.tags.any {
it == "✨ ReVanced" || it == "💊 Manager"
}
val isUnread = announcement.id !in readAnnouncements
isNotArchived && hasRelevantTag && isUnread
!announcement.isArchived && hasRelevantTag && isUnread
}
}
}

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.