mirror of
https://github.com/ReVanced/revanced-manager
synced 2026-04-25 17:15:36 +02:00
chore: Merge branch dev to main (#3184)
This commit is contained in:
@@ -1,3 +1,31 @@
|
||||
# app [2.5.0-dev.2](https://github.com/ReVanced/revanced-manager/compare/v2.5.0-dev.1...v2.5.0-dev.2) (2026-03-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Show release dates and patch count ([#3185](https://github.com/ReVanced/revanced-manager/issues/3185)) ([d5a5ec6](https://github.com/ReVanced/revanced-manager/commit/d5a5ec62a4c57a8b6bc0fcc23c8b74c65be2c66e))
|
||||
|
||||
# app [2.5.0-dev.1](https://github.com/ReVanced/revanced-manager/compare/v2.4.1-dev.1...v2.5.0-dev.1) (2026-03-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Update screen crashing ([cd9d2eb](https://github.com/ReVanced/revanced-manager/commit/cd9d2ebd06e0abc5b8b10a752d8c92ca65abc1e3))
|
||||
* Weird padding in Update screen ([2e96c58](https://github.com/ReVanced/revanced-manager/commit/2e96c58f7bc3afc9fd636b76a2204b1b995f8da6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Improve Updates settings screen ([4ce823c](https://github.com/ReVanced/revanced-manager/commit/4ce823c8c0f4de2bea6c07b362741a980b392e79))
|
||||
|
||||
## app [2.4.1-dev.1](https://github.com/ReVanced/revanced-manager/compare/v2.4.0...v2.4.1-dev.1) (2026-03-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use ease out quart for screen transitions ([b69f7c2](https://github.com/ReVanced/revanced-manager/commit/b69f7c2ba8d320e10c3558681294f8bac93618ae))
|
||||
* Use em-space and bigger bullet symbols in announcement screen ([2538b6a](https://github.com/ReVanced/revanced-manager/commit/2538b6a7553a1d9366d9f2345a6a471381f18d88))
|
||||
|
||||
# app [2.4.0](https://github.com/ReVanced/revanced-manager/compare/v2.3.0...v2.4.0) (2026-03-23)
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
version = 2.4.0
|
||||
version = 2.5.0-dev.2
|
||||
|
||||
493
app/schemas/app.revanced.manager.data.room.AppDatabase/4.json
Normal file
493
app/schemas/app.revanced.manager.data.room.AppDatabase/4.json
Normal file
@@ -0,0 +1,493 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 4,
|
||||
"identityHash": "7a99bbb0f5aa995683850a59bdf9f235",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "patch_bundles",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `name` TEXT NOT NULL, `version` TEXT, `source` TEXT NOT NULL, `auto_update` INTEGER NOT NULL, `released_at` INTEGER, PRIMARY KEY(`uid`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "uid",
|
||||
"columnName": "uid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "versionHash",
|
||||
"columnName": "version",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "source",
|
||||
"columnName": "source",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "autoUpdate",
|
||||
"columnName": "auto_update",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "releasedAt",
|
||||
"columnName": "released_at",
|
||||
"affinity": "INTEGER"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"uid"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "patch_selections",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `patch_bundle` INTEGER NOT NULL, `package_name` TEXT NOT NULL, PRIMARY KEY(`uid`), FOREIGN KEY(`patch_bundle`) REFERENCES `patch_bundles`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "uid",
|
||||
"columnName": "uid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "patchBundle",
|
||||
"columnName": "patch_bundle",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "package_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"uid"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_patch_selections_patch_bundle_package_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"patch_bundle",
|
||||
"package_name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_patch_selections_patch_bundle_package_name` ON `${TABLE_NAME}` (`patch_bundle`, `package_name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "patch_bundles",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"patch_bundle"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"uid"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "selected_patches",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`selection` INTEGER NOT NULL, `patch_name` TEXT NOT NULL, PRIMARY KEY(`selection`, `patch_name`), FOREIGN KEY(`selection`) REFERENCES `patch_selections`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "selection",
|
||||
"columnName": "selection",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "patchName",
|
||||
"columnName": "patch_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"selection",
|
||||
"patch_name"
|
||||
]
|
||||
},
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "patch_selections",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"selection"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"uid"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "downloaded_app",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `version` TEXT NOT NULL, `directory` TEXT NOT NULL, `last_used` INTEGER NOT NULL, PRIMARY KEY(`package_name`, `version`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "package_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "version",
|
||||
"columnName": "version",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "directory",
|
||||
"columnName": "directory",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastUsed",
|
||||
"columnName": "last_used",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"package_name",
|
||||
"version"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "installed_app",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`current_package_name` TEXT NOT NULL, `original_package_name` TEXT NOT NULL, `version` TEXT NOT NULL, `install_type` TEXT NOT NULL, PRIMARY KEY(`current_package_name`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "currentPackageName",
|
||||
"columnName": "current_package_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "originalPackageName",
|
||||
"columnName": "original_package_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "version",
|
||||
"columnName": "version",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "installType",
|
||||
"columnName": "install_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"current_package_name"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "applied_patch",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `bundle` INTEGER NOT NULL, `patch_name` TEXT NOT NULL, PRIMARY KEY(`package_name`, `bundle`, `patch_name`), FOREIGN KEY(`package_name`) REFERENCES `installed_app`(`current_package_name`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`bundle`) REFERENCES `patch_bundles`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "package_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "bundle",
|
||||
"columnName": "bundle",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "patchName",
|
||||
"columnName": "patch_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"package_name",
|
||||
"bundle",
|
||||
"patch_name"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_applied_patch_bundle",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"bundle"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_applied_patch_bundle` ON `${TABLE_NAME}` (`bundle`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "installed_app",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"package_name"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"current_package_name"
|
||||
]
|
||||
},
|
||||
{
|
||||
"table": "patch_bundles",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"bundle"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"uid"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "installed_patch_bundle",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`package_name` TEXT NOT NULL, `bundle_uid` INTEGER NOT NULL, `bundle_name` TEXT NOT NULL, `bundle_version` TEXT, PRIMARY KEY(`package_name`, `bundle_uid`), FOREIGN KEY(`package_name`) REFERENCES `installed_app`(`current_package_name`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "package_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "bundleUid",
|
||||
"columnName": "bundle_uid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "bundleName",
|
||||
"columnName": "bundle_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "bundleVersion",
|
||||
"columnName": "bundle_version",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"package_name",
|
||||
"bundle_uid"
|
||||
]
|
||||
},
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "installed_app",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"package_name"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"current_package_name"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "option_groups",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `patch_bundle` INTEGER NOT NULL, `package_name` TEXT NOT NULL, PRIMARY KEY(`uid`), FOREIGN KEY(`patch_bundle`) REFERENCES `patch_bundles`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "uid",
|
||||
"columnName": "uid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "patchBundle",
|
||||
"columnName": "patch_bundle",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "packageName",
|
||||
"columnName": "package_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"uid"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_option_groups_patch_bundle_package_name",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"patch_bundle",
|
||||
"package_name"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_option_groups_patch_bundle_package_name` ON `${TABLE_NAME}` (`patch_bundle`, `package_name`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "patch_bundles",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"patch_bundle"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"uid"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "options",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`group` INTEGER NOT NULL, `patch_name` TEXT NOT NULL, `key` TEXT NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`group`, `patch_name`, `key`), FOREIGN KEY(`group`) REFERENCES `option_groups`(`uid`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "group",
|
||||
"columnName": "group",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "patchName",
|
||||
"columnName": "patch_name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "key",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "value",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"group",
|
||||
"patch_name",
|
||||
"key"
|
||||
]
|
||||
},
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "option_groups",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "NO ACTION",
|
||||
"columns": [
|
||||
"group"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"uid"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "downloaders",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `name` TEXT NOT NULL, `version` TEXT, `source` TEXT NOT NULL, `auto_update` INTEGER NOT NULL, `released_at` INTEGER, PRIMARY KEY(`uid`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "uid",
|
||||
"columnName": "uid",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "versionHash",
|
||||
"columnName": "version",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "source",
|
||||
"columnName": "source",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "autoUpdate",
|
||||
"columnName": "auto_update",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "releasedAt",
|
||||
"columnName": "released_at",
|
||||
"affinity": "INTEGER"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"uid"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7a99bbb0f5aa995683850a59bdf9f235')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,7 @@ import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.EaseInOutQuad
|
||||
import androidx.compose.animation.core.EaseOut
|
||||
import androidx.compose.animation.core.EaseOutQuart
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.slideInHorizontally
|
||||
import androidx.compose.animation.slideOutHorizontally
|
||||
@@ -76,7 +75,6 @@ import org.koin.androidx.compose.koinViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import org.koin.androidx.viewmodel.ext.android.getViewModel as getActivityViewModel
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
@ExperimentalAnimationApi
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -129,24 +127,24 @@ private fun ReVancedManager(vm: MainViewModel) {
|
||||
startDestination = startDestination,
|
||||
enterTransition = {
|
||||
slideInHorizontally(
|
||||
animationSpec = tween(300, easing = EaseInOutQuad),
|
||||
animationSpec = tween(300, easing = EaseOutQuart),
|
||||
initialOffsetX = { it })
|
||||
},
|
||||
exitTransition = {
|
||||
slideOutHorizontally(
|
||||
animationSpec = tween(300, easing = EaseOut),
|
||||
animationSpec = tween(300, easing = EaseOutQuart),
|
||||
targetOffsetX = { -it / 3 })
|
||||
},
|
||||
popEnterTransition = {
|
||||
slideInHorizontally(
|
||||
animationSpec = tween(
|
||||
300,
|
||||
easing = EaseInOutQuad
|
||||
easing = EaseOutQuart
|
||||
), initialOffsetX = { -it / 3 })
|
||||
},
|
||||
popExitTransition = {
|
||||
slideOutHorizontally(
|
||||
animationSpec = tween(300, easing = EaseOut),
|
||||
animationSpec = tween(300, easing = EaseOutQuart),
|
||||
targetOffsetX = { it })
|
||||
}
|
||||
) {
|
||||
@@ -229,7 +227,12 @@ private fun ReVancedManager(vm: MainViewModel) {
|
||||
|
||||
UpdateScreen(
|
||||
onBackClick = navController::popBackStackSafe,
|
||||
vm = koinViewModel { parametersOf(data.downloadOnScreenEntry) }
|
||||
vm = koinViewModel {
|
||||
parametersOf(
|
||||
ChangelogSource.Manager,
|
||||
data.downloadOnScreenEntry
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -26,14 +26,16 @@ import kotlin.random.Random
|
||||
|
||||
@Database(
|
||||
entities = [PatchBundleEntity::class, PatchSelection::class, SelectedPatch::class, DownloadedApp::class, InstalledApp::class, AppliedPatch::class, InstalledPatchBundle::class, OptionGroup::class, Option::class, DownloaderEntity::class],
|
||||
version = 3,
|
||||
version = 4,
|
||||
exportSchema = true,
|
||||
autoMigrations = [
|
||||
AutoMigration(from = 1, to = 2),
|
||||
AutoMigration(
|
||||
from = 2,
|
||||
to = 3,
|
||||
spec = AppDatabase.DeleteTrustedDownloaders::class
|
||||
)
|
||||
),
|
||||
AutoMigration(from = 3, to = 4)
|
||||
]
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
||||
@@ -23,7 +23,7 @@ interface PatchBundleDao {
|
||||
@Query("DELETE FROM patch_bundles WHERE uid = :uid")
|
||||
suspend fun remove(uid: Int)
|
||||
|
||||
@Query("SELECT name, version, auto_update, source FROM patch_bundles WHERE uid = :uid")
|
||||
@Query("SELECT name, version, auto_update, source, released_at FROM patch_bundles WHERE uid = :uid")
|
||||
suspend fun getProps(uid: Int): SourceProperties?
|
||||
|
||||
@Upsert
|
||||
|
||||
@@ -10,5 +10,6 @@ data class PatchBundleEntity(
|
||||
@ColumnInfo(name = "name") val name: String,
|
||||
@ColumnInfo(name = "version") val versionHash: String? = null,
|
||||
@ColumnInfo(name = "source") val source: Source,
|
||||
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
||||
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean,
|
||||
@ColumnInfo(name = "released_at") val releasedAt: Long? = null,
|
||||
) : SourceManager.DatabaseEntity
|
||||
@@ -26,7 +26,7 @@ interface DownloaderDao {
|
||||
@Query("DELETE FROM downloaders WHERE uid = :uid")
|
||||
suspend fun remove(uid: Int)
|
||||
|
||||
@Query("SELECT name, version, auto_update, source FROM downloaders WHERE uid = :uid")
|
||||
@Query("SELECT name, version, auto_update, source, released_at FROM downloaders WHERE uid = :uid")
|
||||
suspend fun getProps(uid: Int): SourceProperties?
|
||||
|
||||
@Upsert
|
||||
|
||||
@@ -10,5 +10,6 @@ data class DownloaderEntity(
|
||||
@ColumnInfo(name = "name") val name: String,
|
||||
@ColumnInfo(name = "version") val versionHash: String? = null,
|
||||
@ColumnInfo(name = "source") val source: Source,
|
||||
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
||||
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean,
|
||||
@ColumnInfo(name = "released_at") val releasedAt: Long? = null
|
||||
) : SourceManager.DatabaseEntity
|
||||
@@ -34,5 +34,6 @@ data class SourceProperties(
|
||||
@ColumnInfo(name = "name") val name: String,
|
||||
@ColumnInfo(name = "version") val versionHash: String? = null,
|
||||
@ColumnInfo(name = "source") val source: Source,
|
||||
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
|
||||
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean,
|
||||
@ColumnInfo(name = "released_at") val releasedAt: Long? = null,
|
||||
)
|
||||
@@ -26,6 +26,9 @@ import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toInstant
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.io.File
|
||||
@@ -114,7 +117,12 @@ abstract class SourceManager<DB : SourceManager.DatabaseEntity, LOADED, OUTPUT>(
|
||||
val newName = src.loaded?.let(::realNameOf).takeIf { it != src.name }
|
||||
?: return@syncName
|
||||
|
||||
updateDb(uid) { it.copy(name = newName) }
|
||||
updateDb(uid) {
|
||||
it.copy(
|
||||
name = newName,
|
||||
releasedAt = (src as? RemoteSource)?.releasedAt?.toEpochMillis()
|
||||
)
|
||||
}
|
||||
sources[uid] = src.copy(name = newName)
|
||||
}
|
||||
|
||||
@@ -141,7 +149,7 @@ abstract class SourceManager<DB : SourceManager.DatabaseEntity, LOADED, OUTPUT>(
|
||||
private suspend fun createEntity(
|
||||
name: String,
|
||||
source: SourceInfo,
|
||||
autoUpdate: Boolean = false
|
||||
autoUpdate: Boolean = false,
|
||||
) =
|
||||
entityFromProps(
|
||||
uid = generateUid(),
|
||||
@@ -150,6 +158,7 @@ abstract class SourceManager<DB : SourceManager.DatabaseEntity, LOADED, OUTPUT>(
|
||||
versionHash = null,
|
||||
source = source,
|
||||
autoUpdate = autoUpdate,
|
||||
releasedAt = null,
|
||||
)
|
||||
).also {
|
||||
dbUpsert(it)
|
||||
@@ -172,6 +181,7 @@ abstract class SourceManager<DB : SourceManager.DatabaseEntity, LOADED, OUTPUT>(
|
||||
versionHash = new.versionHash,
|
||||
source = new.source,
|
||||
autoUpdate = new.autoUpdate,
|
||||
releasedAt = new.releasedAt,
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -238,7 +248,7 @@ abstract class SourceManager<DB : SourceManager.DatabaseEntity, LOADED, OUTPUT>(
|
||||
this@SourceManager.store.state.value.sources.values.filterIsInstance<APISource<LOADED>>()
|
||||
.forEach { src ->
|
||||
with(src) { deleteLocalFile() }
|
||||
updateDb(src.uid) { it.copy(versionHash = null) }
|
||||
updateDb(src.uid) { it.copy(versionHash = null, releasedAt = null) }
|
||||
}
|
||||
|
||||
doReload(state)
|
||||
@@ -308,15 +318,15 @@ abstract class SourceManager<DB : SourceManager.DatabaseEntity, LOADED, OUTPUT>(
|
||||
async update@{
|
||||
Log.d(tag, "Updating: ${it.name}")
|
||||
|
||||
val newVersion = it.runCatching {
|
||||
val updateResult = it.runCatching {
|
||||
when {
|
||||
redownload -> downloadLatest()
|
||||
checkOnly -> getUpdateInfo()?.version
|
||||
checkOnly -> getUpdateInfo()?.let { info -> RemoteSource.UpdateResult(info.version, info.createdAt) }
|
||||
else -> update()
|
||||
} ?: return@update null
|
||||
}
|
||||
|
||||
it to newVersion
|
||||
it to updateResult
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
@@ -332,7 +342,7 @@ abstract class SourceManager<DB : SourceManager.DatabaseEntity, LOADED, OUTPUT>(
|
||||
|
||||
var hasErrors = false
|
||||
results.forEach { (src, result) ->
|
||||
result.getOrNull()?.let { newVersionHash ->
|
||||
result.getOrNull()?.let { updateResult ->
|
||||
if (checkOnly) {
|
||||
outdated.add(src.uid)
|
||||
return@let
|
||||
@@ -340,7 +350,11 @@ abstract class SourceManager<DB : SourceManager.DatabaseEntity, LOADED, OUTPUT>(
|
||||
|
||||
val name = src.loaded?.let(::realNameOf) ?: src.name
|
||||
updateDb(src.uid) {
|
||||
it.copy(versionHash = newVersionHash, name = name)
|
||||
it.copy(
|
||||
versionHash = updateResult.versionHash,
|
||||
name = name,
|
||||
releasedAt = updateResult.releasedAt.toEpochMillis()
|
||||
)
|
||||
}
|
||||
}
|
||||
result.exceptionOrNull()?.let {
|
||||
@@ -378,4 +392,6 @@ abstract class SourceManager<DB : SourceManager.DatabaseEntity, LOADED, OUTPUT>(
|
||||
interface DatabaseEntity {
|
||||
val uid: Int
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun LocalDateTime.toEpochMillis() = toInstant(TimeZone.UTC).toEpochMilliseconds()
|
||||
@@ -30,9 +30,11 @@ import app.revanced.manager.network.downloader.DownloaderPackage
|
||||
import app.revanced.manager.util.PM
|
||||
import dalvik.system.PathClassLoader
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
import java.lang.reflect.Modifier
|
||||
import kotlin.time.Instant
|
||||
import app.revanced.manager.data.room.sources.Source as SourceInfo
|
||||
|
||||
@OptIn(DownloaderHostApi::class)
|
||||
@@ -61,7 +63,12 @@ class DownloaderRepository(
|
||||
override fun loadEntity(entity: DownloaderEntity): Source<DownloaderPackage> = with(entity) {
|
||||
val file = directoryOf(uid).resolve("downloader.jar")
|
||||
val actualName =
|
||||
entity.name.ifEmpty { app.getString(if (uid == 0) R.string.auto_updates_dialog_downloaders else R.string.source_name_fallback) }
|
||||
name.ifEmpty { app.getString(if (uid == 0) R.string.auto_updates_dialog_downloaders else R.string.source_name_fallback) }
|
||||
|
||||
val releasedAt = entity.releasedAt?.let {
|
||||
Instant.fromEpochMilliseconds(it)
|
||||
.toLocalDateTime(kotlinx.datetime.TimeZone.UTC)
|
||||
}
|
||||
|
||||
return when (source) {
|
||||
is SourceInfo.Local -> LocalSource(actualName, uid, null, file, loader)
|
||||
@@ -69,6 +76,7 @@ class DownloaderRepository(
|
||||
actualName,
|
||||
uid,
|
||||
versionHash,
|
||||
releasedAt,
|
||||
null,
|
||||
file,
|
||||
SourceInfo.API.SENTINEL,
|
||||
@@ -80,6 +88,7 @@ class DownloaderRepository(
|
||||
actualName,
|
||||
uid,
|
||||
versionHash,
|
||||
releasedAt,
|
||||
null,
|
||||
file,
|
||||
source.url.toString(),
|
||||
@@ -97,7 +106,8 @@ class DownloaderRepository(
|
||||
name = props.name,
|
||||
versionHash = props.versionHash,
|
||||
source = props.source,
|
||||
autoUpdate = props.autoUpdate
|
||||
autoUpdate = props.autoUpdate,
|
||||
releasedAt = props.releasedAt
|
||||
)
|
||||
|
||||
override fun realNameOf(loaded: DownloaderPackage) = loaded.name
|
||||
|
||||
@@ -1,22 +1,43 @@
|
||||
package app.revanced.manager.domain.repository
|
||||
|
||||
import app.revanced.manager.BuildConfig
|
||||
import app.revanced.manager.network.api.ReVancedAPI
|
||||
import app.revanced.manager.network.dto.ReVancedAsset
|
||||
import app.revanced.manager.network.utils.getOrThrow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
|
||||
class ManagerUpdateRepository(
|
||||
private val reVancedAPI: ReVancedAPI
|
||||
) {
|
||||
private val _availableVersion = MutableStateFlow<String?>(null)
|
||||
val availableVersion = _availableVersion.asStateFlow()
|
||||
private val asset: ReVancedAsset? = null
|
||||
private val _releasedAt = MutableStateFlow<LocalDateTime?>(null)
|
||||
private val _version = MutableStateFlow<String?>(null)
|
||||
private val _hasUpdate = MutableStateFlow(false)
|
||||
|
||||
suspend fun refreshAvailableVersion(): String? {
|
||||
val version = reVancedAPI.getAppUpdate()?.version
|
||||
_availableVersion.value = version
|
||||
return version
|
||||
val releasedAt = _releasedAt.asStateFlow()
|
||||
val hasUpdate = _hasUpdate.asStateFlow()
|
||||
val version = _version.asStateFlow()
|
||||
|
||||
suspend fun refresh(): ReVancedAsset {
|
||||
val update = reVancedAPI.getLatestAppInfo().getOrThrow()
|
||||
|
||||
_releasedAt.value = update.createdAt
|
||||
_version.value = update.version
|
||||
_hasUpdate.value = update.version.removePrefix("v") != BuildConfig.VERSION_NAME
|
||||
|
||||
return update
|
||||
}
|
||||
|
||||
fun clearAvailableVersion() {
|
||||
_availableVersion.value = null
|
||||
suspend fun getUpdateOrNull(refetch: Boolean = false): ReVancedAsset? {
|
||||
val asset = if (refetch || asset == null) refresh() else null
|
||||
return asset.takeIf { _hasUpdate.value }
|
||||
}
|
||||
|
||||
fun clearState() {
|
||||
_releasedAt.value = null
|
||||
_version.value = null
|
||||
_hasUpdate.value = false
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import app.revanced.manager.domain.sources.LocalPatchBundle
|
||||
import app.revanced.manager.domain.sources.PatchBundleSource
|
||||
import app.revanced.manager.domain.manager.SourceManager
|
||||
import app.revanced.manager.domain.sources.Loader
|
||||
import app.revanced.manager.domain.sources.RemotePatchBundle
|
||||
import app.revanced.manager.domain.sources.Source
|
||||
import app.revanced.manager.patcher.patch.PatchInfo
|
||||
import app.revanced.manager.patcher.patch.PatchBundle
|
||||
@@ -27,9 +28,13 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toInstant
|
||||
import kotlinx.datetime.toLocalDateTime
|
||||
import java.io.File
|
||||
import kotlin.collections.map
|
||||
import kotlin.text.ifEmpty
|
||||
import kotlin.time.Instant
|
||||
|
||||
private typealias Info = PersistentMap<Int, PatchBundleInfo.Global>
|
||||
|
||||
@@ -58,12 +63,18 @@ class PatchBundleRepository(
|
||||
val actualName =
|
||||
entity.name.ifEmpty { app.getString(if (uid == 0) R.string.patches_name_default else R.string.source_name_fallback) }
|
||||
|
||||
val releasedAt = entity.releasedAt?.let {
|
||||
Instant.fromEpochMilliseconds(it)
|
||||
.toLocalDateTime(TimeZone.UTC)
|
||||
}
|
||||
|
||||
return when (source) {
|
||||
is SourceInfo.Local -> LocalPatchBundle(actualName, uid, null, file, PatchBundleLoader)
|
||||
is SourceInfo.API -> APIPatchBundle(
|
||||
actualName,
|
||||
uid,
|
||||
versionHash,
|
||||
releasedAt,
|
||||
null,
|
||||
file,
|
||||
SourceInfo.API.SENTINEL,
|
||||
@@ -75,6 +86,7 @@ class PatchBundleRepository(
|
||||
actualName,
|
||||
uid,
|
||||
versionHash,
|
||||
releasedAt,
|
||||
null,
|
||||
file,
|
||||
source.url.toString(),
|
||||
@@ -92,7 +104,8 @@ class PatchBundleRepository(
|
||||
name = props.name,
|
||||
versionHash = props.versionHash,
|
||||
source = props.source,
|
||||
autoUpdate = props.autoUpdate
|
||||
autoUpdate = props.autoUpdate,
|
||||
releasedAt = props.releasedAt
|
||||
)
|
||||
|
||||
override fun realNameOf(loaded: PatchBundle) = loaded.manifestAttributes?.name
|
||||
@@ -173,6 +186,7 @@ class PatchBundleRepository(
|
||||
this[src.uid] = PatchBundleInfo.Global(
|
||||
src.name,
|
||||
bundle.manifestAttributes?.version,
|
||||
(src as? RemotePatchBundle)?.releasedAt,
|
||||
src.uid,
|
||||
result.getOrThrow().toList()
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ import kotlinx.coroutines.withContext
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.io.File
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
|
||||
typealias RemotePatchBundle = RemoteSource<PatchBundle>
|
||||
typealias JsonPatchBundle = JsonSource<PatchBundle>
|
||||
@@ -22,17 +23,28 @@ sealed class RemoteSource<T>(
|
||||
name: String,
|
||||
uid: Int,
|
||||
protected val versionHash: String?,
|
||||
val releasedAt: LocalDateTime?,
|
||||
error: Throwable?,
|
||||
file: File,
|
||||
val endpoint: String,
|
||||
val autoUpdate: Boolean,
|
||||
loader: Loader<T>
|
||||
) : Source<T>(name, uid, error, file, loader), KoinComponent {
|
||||
data class UpdateResult(val versionHash: String, val releasedAt: LocalDateTime)
|
||||
|
||||
protected val http: HttpService by inject()
|
||||
|
||||
protected abstract suspend fun getLatestInfo(): ReVancedAsset
|
||||
abstract fun copy(error: Throwable? = this.error, name: String = this.name, autoUpdate: Boolean = this.autoUpdate): RemoteSource<T>
|
||||
override fun copy(error: Throwable?, name: String): RemoteSource<T> = copy(error, name, this.autoUpdate)
|
||||
abstract fun copy(
|
||||
error: Throwable? = this.error,
|
||||
name: String = this.name,
|
||||
autoUpdate: Boolean = this.autoUpdate,
|
||||
versionHash: String? = this.versionHash,
|
||||
releasedAt: LocalDateTime? = this.releasedAt
|
||||
): RemoteSource<T>
|
||||
|
||||
override fun copy(error: Throwable?, name: String): RemoteSource<T> =
|
||||
copy(error, name, this.autoUpdate, this.versionHash, this.releasedAt)
|
||||
|
||||
private suspend fun download(info: ReVancedAsset) = withContext(Dispatchers.IO) {
|
||||
outputStream().use {
|
||||
@@ -41,15 +53,17 @@ sealed class RemoteSource<T>(
|
||||
}
|
||||
}
|
||||
|
||||
info.version
|
||||
UpdateResult(info.version, info.createdAt)
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the latest version regardless if there is a new update available.
|
||||
*/
|
||||
suspend fun ActionContext.downloadLatest() = download(getLatestInfo())
|
||||
suspend fun ActionContext.getUpdateInfo() = getLatestInfo().takeUnless { hasInstalled() && it.version == versionHash }
|
||||
suspend fun ActionContext.update(): String? = withContext(Dispatchers.IO) {
|
||||
suspend fun ActionContext.getUpdateInfo() =
|
||||
getLatestInfo().takeUnless { hasInstalled() && it.version == versionHash }
|
||||
|
||||
suspend fun ActionContext.update(): UpdateResult? = withContext(Dispatchers.IO) {
|
||||
getUpdateInfo()?.let { download(it) }
|
||||
}
|
||||
|
||||
@@ -62,22 +76,30 @@ class JsonSource<T>(
|
||||
name: String,
|
||||
uid: Int,
|
||||
versionHash: String?,
|
||||
releasedAt: LocalDateTime?,
|
||||
error: Throwable?,
|
||||
file: File,
|
||||
endpoint: String,
|
||||
autoUpdate: Boolean,
|
||||
loader: Loader<T>
|
||||
) : RemoteSource<T>(name, uid, versionHash, error, file, endpoint, autoUpdate, loader) {
|
||||
) : RemoteSource<T>(name, uid, versionHash, releasedAt, error, file, endpoint, autoUpdate, loader) {
|
||||
override suspend fun getLatestInfo() = withContext(Dispatchers.IO) {
|
||||
http.request<ReVancedAsset> {
|
||||
url(endpoint)
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
override fun copy(error: Throwable?, name: String, autoUpdate: Boolean) = JsonSource(
|
||||
override fun copy(
|
||||
error: Throwable?,
|
||||
name: String,
|
||||
autoUpdate: Boolean,
|
||||
versionHash: String?,
|
||||
releasedAt: LocalDateTime?
|
||||
) = JsonSource(
|
||||
name,
|
||||
uid,
|
||||
versionHash,
|
||||
releasedAt,
|
||||
error,
|
||||
file,
|
||||
endpoint,
|
||||
@@ -90,20 +112,28 @@ class APISource<T>(
|
||||
name: String,
|
||||
uid: Int,
|
||||
versionHash: String?,
|
||||
releasedAt: LocalDateTime?,
|
||||
error: Throwable?,
|
||||
file: File,
|
||||
endpoint: String,
|
||||
autoUpdate: Boolean,
|
||||
loader: Loader<T>,
|
||||
private val getUpdate: suspend ReVancedAPI.() -> APIResponse<ReVancedAsset>
|
||||
) : RemoteSource<T>(name, uid, versionHash, error, file, endpoint, autoUpdate, loader) {
|
||||
) : RemoteSource<T>(name, uid, versionHash, releasedAt, error, file, endpoint, autoUpdate, loader) {
|
||||
private val api: ReVancedAPI by inject()
|
||||
|
||||
override suspend fun getLatestInfo() = api.getUpdate().getOrThrow()
|
||||
override fun copy(error: Throwable?, name: String, autoUpdate: Boolean) = APISource(
|
||||
override fun copy(
|
||||
error: Throwable?,
|
||||
name: String,
|
||||
autoUpdate: Boolean,
|
||||
versionHash: String?,
|
||||
releasedAt: LocalDateTime?
|
||||
) = APISource(
|
||||
name,
|
||||
uid,
|
||||
versionHash,
|
||||
releasedAt,
|
||||
error,
|
||||
file,
|
||||
endpoint,
|
||||
|
||||
@@ -43,9 +43,6 @@ class ReVancedAPI(
|
||||
|
||||
suspend fun getAnnouncements() = request<List<ReVancedAnnouncement>>("announcements")
|
||||
|
||||
suspend fun getAppUpdate() =
|
||||
getLatestAppInfo().getOrThrow().takeIf { it.version.removePrefix("v") != BuildConfig.VERSION_NAME }
|
||||
|
||||
suspend fun getLatestAppInfo() =
|
||||
request<ReVancedAsset>("manager${prefs.useManagerPrereleases.prereleaseString()}")
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.revanced.manager.patcher.patch
|
||||
|
||||
import app.revanced.manager.util.PatchSelection
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
|
||||
/**
|
||||
* A base class for storing [PatchBundle] metadata.
|
||||
@@ -16,6 +17,11 @@ sealed class PatchBundleInfo {
|
||||
*/
|
||||
abstract val version: String?
|
||||
|
||||
/**
|
||||
* When this bundle was released. Only applicable for remote bundles.
|
||||
*/
|
||||
abstract val releasedAt: LocalDateTime?
|
||||
|
||||
/**
|
||||
* The unique ID of the bundle.
|
||||
*/
|
||||
@@ -34,6 +40,7 @@ sealed class PatchBundleInfo {
|
||||
data class Global(
|
||||
override val name: String,
|
||||
override val version: String?,
|
||||
override val releasedAt: LocalDateTime?,
|
||||
override val uid: Int,
|
||||
override val patches: List<PatchInfo>
|
||||
) : PatchBundleInfo() {
|
||||
@@ -64,6 +71,7 @@ sealed class PatchBundleInfo {
|
||||
return Scoped(
|
||||
name,
|
||||
this.version,
|
||||
releasedAt,
|
||||
uid,
|
||||
relevantPatches,
|
||||
compatible,
|
||||
@@ -85,6 +93,7 @@ sealed class PatchBundleInfo {
|
||||
data class Scoped(
|
||||
override val name: String,
|
||||
override val version: String?,
|
||||
override val releasedAt: LocalDateTime?,
|
||||
override val uid: Int,
|
||||
override val patches: List<PatchInfo>,
|
||||
val compatible: List<PatchInfo>,
|
||||
|
||||
@@ -3,14 +3,13 @@ package app.revanced.manager.ui.component
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Campaign
|
||||
@@ -20,13 +19,8 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -41,6 +35,7 @@ import app.revanced.manager.util.relativeTime
|
||||
fun ChangelogList(
|
||||
changelogs: LazyPagingItems<ReVancedAssetHistory>,
|
||||
modifier: Modifier = Modifier,
|
||||
contentPadding: PaddingValues = PaddingValues(0.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
@@ -67,7 +62,8 @@ fun ChangelogList(
|
||||
|
||||
LazyColumnWithScrollbar(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState
|
||||
state = listState,
|
||||
contentPadding = contentPadding
|
||||
) {
|
||||
items(
|
||||
count = changelogs.itemCount,
|
||||
|
||||
@@ -21,6 +21,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.state.ToggleableState
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -28,6 +30,7 @@ import app.revanced.manager.R
|
||||
import app.revanced.manager.patcher.patch.PatchBundleInfo
|
||||
import app.revanced.manager.ui.component.TooltipIconButton
|
||||
import app.revanced.manager.ui.component.haptics.HapticTriStateCheckbox
|
||||
import app.revanced.manager.util.relativeTime
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -70,13 +73,24 @@ fun SourceSectionHeader(
|
||||
Text(text = bundle.name)
|
||||
},
|
||||
supportingContent = {
|
||||
val patchCount = bundle.patches.size
|
||||
val version = bundle.version?.takeIf { it.isNotBlank() }
|
||||
val releasedAt = bundle.releasedAt?.relativeTime(LocalContext.current)
|
||||
if (version == null && loadIssue == null) return@ListItem
|
||||
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
version?.let {
|
||||
Text(
|
||||
text = it,
|
||||
text = listOf(
|
||||
// Show release date only when view-only
|
||||
if (readOnly && releasedAt != null) "v$it\u2002($releasedAt)"
|
||||
else it,
|
||||
pluralStringResource(
|
||||
R.plurals.patch_count,
|
||||
patchCount,
|
||||
patchCount
|
||||
)
|
||||
).joinToString("\u2002\u2022\u2002"),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ fun AnnouncementScreen(
|
||||
},
|
||||
subtitle = {
|
||||
val createDate = announcement.createdAt.toLocalDateTime(TimeZone.UTC).relativeTime(LocalContext.current)
|
||||
Text("$createDate · ${announcement.author}")
|
||||
Text("$createDate\u2002\u2022\u2002${announcement.author}")
|
||||
},
|
||||
navigationIcon = {
|
||||
TooltipIconButton(
|
||||
|
||||
@@ -47,9 +47,11 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -58,6 +60,7 @@ import app.revanced.manager.R
|
||||
import app.revanced.manager.domain.repository.ChangelogSource
|
||||
import app.revanced.manager.domain.sources.Extensions.asRemoteOrNull
|
||||
import app.revanced.manager.domain.sources.LocalSource
|
||||
import app.revanced.manager.domain.sources.RemotePatchBundle
|
||||
import app.revanced.manager.domain.sources.Source
|
||||
import app.revanced.manager.ui.component.ColumnWithScrollbar
|
||||
import app.revanced.manager.ui.component.ConfirmDialog
|
||||
@@ -69,6 +72,8 @@ import app.revanced.manager.ui.component.haptics.HapticSwitch
|
||||
import app.revanced.manager.ui.component.settings.SafeguardBooleanItem
|
||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||
import app.revanced.manager.ui.viewmodel.BundleInformationViewModel
|
||||
import app.revanced.manager.util.relativeTime
|
||||
import java.util.Locale.getDefault
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
@@ -125,7 +130,17 @@ fun BundleInformationScreen(
|
||||
val dot = "\u2022" // •
|
||||
val emSpace = "\u2002" // en space, roughly half character width
|
||||
val separator = "$emSpace$dot$emSpace"
|
||||
Text("$subtitleAuthor$separator$subtitleVersion")
|
||||
Text(text = buildAnnotatedString {
|
||||
append("$subtitleAuthor$separator$subtitleVersion")
|
||||
src.asRemoteOrNull?.releasedAt?.let {
|
||||
val releaseDate = it.relativeTime(
|
||||
LocalContext.current
|
||||
).lowercase(getDefault())
|
||||
|
||||
append("\u2002($releaseDate)")
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
} else {
|
||||
null
|
||||
|
||||
@@ -47,7 +47,6 @@ import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
@@ -141,7 +140,8 @@ fun DashboardScreen(
|
||||
!suggestedVersionSafeguard
|
||||
}
|
||||
}
|
||||
val availableUpdate by vm.availableManagerUpdate.collectAsStateWithLifecycle()
|
||||
val hasUpdate by vm.hasUpdate.collectAsStateWithLifecycle()
|
||||
val updateVersion by vm.updateVersion.collectAsStateWithLifecycle()
|
||||
val androidContext = LocalContext.current
|
||||
val resources = LocalResources.current
|
||||
val logoPainter = rememberDrawablePainter(drawable = remember(resources) {
|
||||
@@ -207,12 +207,12 @@ fun DashboardScreen(
|
||||
}
|
||||
|
||||
var showUpdateDialog by rememberSaveable { mutableStateOf(true) }
|
||||
if (managerAutoUpdates && showUpdateDialog && showManagerUpdateDialogOnLaunch && availableUpdate != null) {
|
||||
if (managerAutoUpdates && showUpdateDialog && showManagerUpdateDialogOnLaunch && hasUpdate) {
|
||||
AvailableUpdateDialog(
|
||||
onDismiss = { showUpdateDialog = false },
|
||||
setShowManagerUpdateDialogOnLaunch = vm::setShowManagerUpdateDialogOnLaunch,
|
||||
onConfirm = onUpdateClick,
|
||||
newVersion = availableUpdate!!
|
||||
newVersion = updateVersion!!
|
||||
)
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ fun DashboardScreen(
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (availableUpdate != null) {
|
||||
if (updateVersion != null) {
|
||||
TooltipIconButton(
|
||||
onClick = onUpdateClick,
|
||||
tooltip = stringResource(R.string.update),
|
||||
|
||||
@@ -110,12 +110,14 @@ fun UpdateScreen(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier.padding(paddingValues).fillMaxSize(),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
if (vm.state == State.DOWNLOADING)
|
||||
LinearWavyProgressIndicator(
|
||||
progress = { vm.downloadProgress },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.padding(top = paddingValues.calculateTopPadding())
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
AnimatedVisibility(visible = vm.showInternetCheckDialog) {
|
||||
@@ -125,7 +127,10 @@ fun UpdateScreen(
|
||||
)
|
||||
}
|
||||
|
||||
ChangelogList(changelogs = changelogs, modifier = Modifier.padding(paddingValues))
|
||||
ChangelogList(
|
||||
changelogs = changelogs,
|
||||
contentPadding = paddingValues
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Update
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.icons.outlined.Download
|
||||
import androidx.compose.material.icons.outlined.SignalWifiOff
|
||||
@@ -35,7 +34,9 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.revanced.manager.R
|
||||
@@ -50,8 +51,10 @@ import app.revanced.manager.ui.component.haptics.HapticSwitch
|
||||
import app.revanced.manager.ui.component.settings.SafeguardBooleanItem
|
||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||
import app.revanced.manager.ui.viewmodel.DownloadsViewModel
|
||||
import app.revanced.manager.util.relativeTime
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import java.util.Locale.getDefault
|
||||
|
||||
@OptIn(
|
||||
ExperimentalMaterial3Api::class,
|
||||
@@ -105,7 +108,20 @@ fun DownloaderInfoScreen(
|
||||
MediumFlexibleTopAppBar(
|
||||
title = { Text(appName) },
|
||||
subtitle = version.takeIf { it.isNotEmpty() }?.let {
|
||||
{ Text("v$it") }
|
||||
{
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
append("v$it")
|
||||
if (remote?.releasedAt != null) {
|
||||
val releaseDate = remote.releasedAt.relativeTime(
|
||||
LocalContext.current
|
||||
).lowercase(getDefault())
|
||||
|
||||
append("\u2002($releaseDate)")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
TooltipIconButton(
|
||||
|
||||
@@ -43,11 +43,16 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.data.room.apps.downloaded.DownloadedApp
|
||||
import app.revanced.manager.domain.sources.Extensions.asRemoteOrNull
|
||||
import app.revanced.manager.domain.sources.Source
|
||||
import app.revanced.manager.domain.sources.Source.State
|
||||
import app.revanced.manager.network.downloader.DownloaderPackage
|
||||
@@ -62,8 +67,10 @@ import app.revanced.manager.ui.component.haptics.HapticCheckbox
|
||||
import app.revanced.manager.ui.component.sources.ImportSourceDialog
|
||||
import app.revanced.manager.ui.component.sources.ImportSourceDialogStrings
|
||||
import app.revanced.manager.ui.viewmodel.DownloadsViewModel
|
||||
import app.revanced.manager.util.relativeTime
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import java.util.Locale.getDefault
|
||||
|
||||
private enum class DownloadsTab(
|
||||
val titleResId: Int,
|
||||
@@ -317,16 +324,28 @@ private fun DownloaderItem(
|
||||
Text(source.name, style = MaterialTheme.typography.bodyLarge)
|
||||
},
|
||||
supportingContent = {
|
||||
val stateText =
|
||||
when (source.state) {
|
||||
is State.Available<*> -> null
|
||||
is State.Failed -> R.string.downloader_state_failed
|
||||
is State.Missing -> R.string.downloader_state_missing
|
||||
}
|
||||
|
||||
val version = source.loaded?.version
|
||||
val relativeTime =
|
||||
(source.asRemoteOrNull)?.releasedAt?.relativeTime(LocalContext.current)
|
||||
|
||||
Text(
|
||||
stringResource(
|
||||
when (source.state) {
|
||||
is State.Available<*> -> R.string.downloader_state_loaded
|
||||
is State.Failed -> R.string.downloader_state_failed
|
||||
is State.Missing -> R.string.downloader_state_missing
|
||||
text = buildAnnotatedString {
|
||||
append(if (relativeTime != null) "v$version\u2002($relativeTime)" else "v$version")
|
||||
// Error colored state text shown when state isn't available
|
||||
if (stateText != null) withStyle(SpanStyle(color = MaterialTheme.colorScheme.error)) {
|
||||
append("\u2002\u2022\u2002")
|
||||
append(stringResource(stateText))
|
||||
}
|
||||
)
|
||||
},
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
},
|
||||
trailingContent = source.loaded?.version?.let { @Composable { Text(it) } }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package app.revanced.manager.ui.screen.settings.update
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -18,7 +19,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Update
|
||||
import androidx.compose.material.icons.outlined.Download
|
||||
import androidx.compose.material.icons.outlined.WorkOutline
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
@@ -55,7 +55,7 @@ import app.revanced.manager.ui.component.TooltipIconButton
|
||||
import app.revanced.manager.ui.component.settings.BooleanItem
|
||||
import app.revanced.manager.ui.component.settings.SafeguardBooleanItem
|
||||
import app.revanced.manager.ui.viewmodel.UpdatesSettingsViewModel
|
||||
import app.revanced.manager.util.toast
|
||||
import app.revanced.manager.util.relativeTime
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
@@ -69,10 +69,11 @@ fun UpdatesSettingsScreen(
|
||||
vm: UpdatesSettingsViewModel = koinViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val resources = LocalResources.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var checkingForUpdate by remember { mutableStateOf(false) }
|
||||
val availableUpdate by vm.availableManagerUpdate.collectAsStateWithLifecycle()
|
||||
val managerVersion by vm.managerVersion.collectAsStateWithLifecycle()
|
||||
val hasUpdate by vm.hasUpdate.collectAsStateWithLifecycle()
|
||||
val updateReleasedAt by vm.updateReleasedAt.collectAsStateWithLifecycle()
|
||||
val scrollState = androidx.compose.foundation.rememberScrollState()
|
||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(
|
||||
canScroll = {
|
||||
@@ -112,15 +113,14 @@ fun UpdatesSettingsScreen(
|
||||
enabled = !checkingForUpdate,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
if (availableUpdate != null) {
|
||||
if (hasUpdate) {
|
||||
onUpdateClick()
|
||||
return@launch
|
||||
}
|
||||
|
||||
checkingForUpdate = true
|
||||
try {
|
||||
val version = vm.checkForUpdates()
|
||||
if (!version.isNullOrEmpty()) onUpdateClick()
|
||||
if (vm.checkUpdates()) onUpdateClick()
|
||||
} finally {
|
||||
checkingForUpdate = false
|
||||
}
|
||||
@@ -144,7 +144,7 @@ fun UpdatesSettingsScreen(
|
||||
text = stringResource(
|
||||
when {
|
||||
checkingForUpdate -> R.string.update_check
|
||||
availableUpdate != null -> R.string.view_update
|
||||
hasUpdate -> R.string.view_update
|
||||
else -> R.string.manual_update_check
|
||||
}
|
||||
)
|
||||
@@ -164,7 +164,13 @@ fun UpdatesSettingsScreen(
|
||||
) {
|
||||
ListSection(
|
||||
title = stringResource(R.string.manager),
|
||||
leadingContent = { Icon(Icons.Outlined.WorkOutline, contentDescription = null, modifier = Modifier.size(18.dp)) }
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Outlined.WorkOutline,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
}
|
||||
) {
|
||||
Surface(
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
@@ -190,11 +196,27 @@ fun UpdatesSettingsScreen(
|
||||
.padding(start = 4.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.app_name),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
|
||||
managerVersion?.let { version ->
|
||||
updateReleasedAt?.let {
|
||||
val releasedAt = it.relativeTime(LocalContext.current)
|
||||
|
||||
Text(
|
||||
text = "$version\u2002\u2022\u2002$releasedAt",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Button(
|
||||
@@ -242,6 +264,7 @@ fun UpdatesSettingsScreen(
|
||||
coroutineScope.launch {
|
||||
vm.useManagerPrereleases.update(value)
|
||||
vm.clearAvailableManagerUpdate()
|
||||
vm.checkUpdates(false)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -40,7 +40,8 @@ class DashboardViewModel(
|
||||
val bundleDownloadError = patchBundleRepository.apiOutageError
|
||||
private val contentResolver: ContentResolver = app.contentResolver
|
||||
|
||||
val availableManagerUpdate = managerUpdateRepository.availableVersion
|
||||
val hasUpdate = managerUpdateRepository.hasUpdate
|
||||
val updateVersion = managerUpdateRepository.version
|
||||
|
||||
val sourcesNotDownloaded = patchBundleRepository.bundleInfoFlow.map { it.isEmpty() }
|
||||
val sourceUpdatesAvailable = combine(
|
||||
@@ -76,7 +77,7 @@ class DashboardViewModel(
|
||||
if (!prefs.managerAutoUpdates.get()) return
|
||||
|
||||
uiSafe(app, R.string.failed_to_check_updates, "Failed to check for updates") {
|
||||
managerUpdateRepository.refreshAvailableVersion()
|
||||
managerUpdateRepository.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import app.revanced.manager.domain.sources.PatchBundleSource
|
||||
import app.revanced.manager.domain.sources.Source.State
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.domain.repository.PatchBundleRepository
|
||||
import app.revanced.manager.domain.sources.Extensions.asRemoteOrNull
|
||||
import app.revanced.manager.domain.sources.Extensions.version
|
||||
import app.revanced.manager.patcher.patch.PatchBundleInfo
|
||||
import app.revanced.manager.patcher.patch.PatchBundleInfo.Extensions.toPatchSelection
|
||||
@@ -375,6 +376,7 @@ private fun PatchSelection.toPersistentPatchSelection(): PersistentPatchSelectio
|
||||
private fun PatchBundleInfo.Global.asReadonlyScoped() = PatchBundleInfo.Scoped(
|
||||
name = name,
|
||||
version = version,
|
||||
releasedAt = releasedAt,
|
||||
uid = uid,
|
||||
patches = patches,
|
||||
compatible = patches,
|
||||
@@ -385,6 +387,7 @@ private fun PatchBundleInfo.Global.asReadonlyScoped() = PatchBundleInfo.Scoped(
|
||||
private fun PatchBundleSource.emptyScopedBundleInfo() = PatchBundleInfo.Scoped(
|
||||
name = name,
|
||||
version = version,
|
||||
releasedAt = (this.asRemoteOrNull)?.releasedAt,
|
||||
uid = uid,
|
||||
patches = emptyList(),
|
||||
compatible = emptyList(),
|
||||
|
||||
@@ -19,6 +19,7 @@ import app.revanced.manager.data.platform.Filesystem
|
||||
import app.revanced.manager.data.platform.NetworkInfo
|
||||
import app.revanced.manager.domain.repository.ChangelogSource
|
||||
import app.revanced.manager.domain.repository.ChangelogsRepository
|
||||
import app.revanced.manager.domain.repository.ManagerUpdateRepository
|
||||
import app.revanced.manager.network.api.ReVancedAPI
|
||||
import app.revanced.manager.network.dto.ReVancedAsset
|
||||
import app.revanced.manager.network.dto.ReVancedAssetHistory
|
||||
@@ -31,9 +32,6 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import org.koin.core.component.inject
|
||||
import ru.solrudev.ackpine.installer.InstallFailure
|
||||
import ru.solrudev.ackpine.installer.PackageInstaller
|
||||
import ru.solrudev.ackpine.installer.createSession
|
||||
@@ -44,15 +42,14 @@ import ru.solrudev.ackpine.session.parameters.Confirmation
|
||||
class UpdateViewModel(
|
||||
private val api: ReVancedAPI,
|
||||
private val source: ChangelogSource,
|
||||
private val downloadOnScreenEntry: Boolean
|
||||
) : ViewModel(), KoinComponent {
|
||||
private val app: Application by inject()
|
||||
private val reVancedAPI: ReVancedAPI by inject()
|
||||
private val http: HttpService by inject()
|
||||
private val networkInfo: NetworkInfo by inject()
|
||||
private val fs: Filesystem by inject()
|
||||
private val ackpineInstaller: PackageInstaller = get()
|
||||
|
||||
private val downloadOnScreenEntry: Boolean,
|
||||
private val app: Application,
|
||||
private val http: HttpService,
|
||||
private val networkInfo: NetworkInfo,
|
||||
private val fs: Filesystem,
|
||||
private val ackpineInstaller: PackageInstaller,
|
||||
private val managerUpdateRepository: ManagerUpdateRepository,
|
||||
) : ViewModel() {
|
||||
// TODO: save state to handle process death.
|
||||
var downloadedSize by mutableLongStateOf(0L)
|
||||
private set
|
||||
@@ -85,7 +82,7 @@ class UpdateViewModel(
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
uiSafe(app, R.string.download_manager_failed, "Failed to download ReVanced Manager") {
|
||||
releaseInfo = reVancedAPI.getAppUpdate()
|
||||
releaseInfo = managerUpdateRepository.getUpdateOrNull()
|
||||
?: throw Exception("No update available")
|
||||
|
||||
if (downloadOnScreenEntry) {
|
||||
|
||||
@@ -18,22 +18,25 @@ class UpdatesSettingsViewModel(
|
||||
val managerAutoUpdates = prefs.managerAutoUpdates
|
||||
val showManagerUpdateDialogOnLaunch = prefs.showManagerUpdateDialogOnLaunch
|
||||
val useManagerPrereleases = prefs.useManagerPrereleases
|
||||
val availableManagerUpdate = managerUpdateRepository.availableVersion
|
||||
val managerVersion = managerUpdateRepository.version
|
||||
val updateReleasedAt = managerUpdateRepository.releasedAt
|
||||
val hasUpdate = managerUpdateRepository.hasUpdate
|
||||
|
||||
suspend fun checkForUpdates(): String? {
|
||||
var availableVersion: String? = null
|
||||
suspend fun checkUpdates(showToast: Boolean = true): Boolean {
|
||||
var hasUpdate = false
|
||||
|
||||
uiSafe(app, R.string.failed_to_check_updates, "Failed to check for updates") {
|
||||
availableVersion = managerUpdateRepository.refreshAvailableVersion()
|
||||
|
||||
if (availableVersion == null)
|
||||
if (managerUpdateRepository.getUpdateOrNull(true) != null) {
|
||||
hasUpdate = true
|
||||
} else if (showToast) {
|
||||
app.toast(app.getString(R.string.no_update_available))
|
||||
}
|
||||
}
|
||||
|
||||
return availableVersion
|
||||
return hasUpdate
|
||||
}
|
||||
|
||||
fun clearAvailableManagerUpdate() {
|
||||
managerUpdateRepository.clearAvailableVersion()
|
||||
managerUpdateRepository.clearState()
|
||||
}
|
||||
}
|
||||
@@ -491,6 +491,7 @@ It’s only compatible with these versions: %2$s</string>
|
||||
<string name="prerelease_title">Use pre-releases?</string>
|
||||
<string name="prereleases_warning">Pre-release versions may be unstable and contain bugs. You may experience crashes, data loss, or other unexpected issues.</string>
|
||||
<string name="changelog">View changelog</string>
|
||||
<string name="updated_ago">Updated %s</string>
|
||||
<string name="changelog_loading">Loading changelog…</string>
|
||||
<string name="changelog_download_fail">Couldn’t download changelog: %s</string>
|
||||
<string name="battery_optimization_notification">Battery optimizations must be turned off in order for ReVanced Manager to work correctly in the background</string>
|
||||
|
||||
Reference in New Issue
Block a user