switch to github as backend

This commit is contained in:
X1nto 2022-03-01 19:01:03 +04:00
parent 73ee8d4f7a
commit 6960cb67b1
45 changed files with 757 additions and 744 deletions

View File

@ -5,6 +5,7 @@ plugins {
id("com.android.application")
kotlin("android")
id("kotlin-parcelize")
kotlin("plugin.serialization")
}
val composeVersion = "1.1.1"
@ -93,6 +94,7 @@ val languages: String get() {
dependencies {
implementation(kotlin("reflect"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
// AndroidX
implementation("androidx.core:core-ktx:1.7.0")
@ -146,6 +148,8 @@ dependencies {
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
implementation("com.squareup.retrofit2:converter-gson:$retrofitVersion")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")

View File

@ -12,11 +12,11 @@
# public *;
#}
-keep class com.vanced.manager.network.model.AppDto {
-keep class com.vanced.manager.network.dto.GithubReleaseDto {
*;
}
-keep class com.vanced.manager.network.model.DataDto {
-keep class com.vanced.manager.network.dto.GithubReleaseAssetDto {
*;
}

View File

@ -15,12 +15,11 @@ class ManagerApplication : Application() {
modules(
apiModule,
customTabsModule,
datasourceModule,
downloaderModule,
installerModule,
mapperModule,
networkModule,
packageManagerModule,
preferenceModule,
repositoryModule,
serviceModule,
viewModelModule,

View File

@ -1,6 +0,0 @@
package com.vanced.manager.core.preferences
data class CheckboxPreference(
val title: String,
val key: String,
)

View File

@ -1,6 +0,0 @@
package com.vanced.manager.core.preferences
data class RadioButtonPreference(
val title: String,
val key: String
)

View File

@ -1,20 +1,13 @@
package com.vanced.manager.core.preferences.holder
import com.vanced.manager.core.preferences.managerBooleanPreference
import com.vanced.manager.core.preferences.managerLongPreference
import com.vanced.manager.core.preferences.managerStringPreference
import com.vanced.manager.core.preferences.managerStringSetPreference
import com.vanced.manager.ui.theme.defAccentColor
var useCustomTabsPref by managerBooleanPreference(USE_CUSTOM_TABS_KEY)
var managerVariantPref by managerStringPreference(
MANAGER_VARIANT_KEY,
MANAGER_VARIANT_DEFAULT_VALUE
)
var managerThemePref by managerStringPreference(MANAGER_THEME_KEY, MANAGER_THEME_DEFAULT_VALUE)
var managerAccentColorPref by managerLongPreference(MANAGER_ACCENT_COLOR_KEY, defAccentColor)
var vancedThemePref by managerStringPreference(APP_VANCED_THEME_KEY, VANCED_THEME_DEFAULT_VALUE)
var vancedVersionPref by managerStringPreference(APP_VANCED_VERSION_KEY, APP_VERSION_DEFAULT_VALUE)
var vancedLanguagesPref by managerStringSetPreference(
@ -22,7 +15,4 @@ var vancedLanguagesPref by managerStringSetPreference(
VANCED_LANGUAGE_DEFAULT_VALUE
)
var musicVersionPref by managerStringPreference(APP_MUSIC_VERSION_KEY, APP_VERSION_DEFAULT_VALUE)
var vancedEnabled by managerBooleanPreference(VANCED_ENABLED_KEY, APP_ENABLED_DEFAULT_VALUE)
var musicEnabled by managerBooleanPreference(MUSIC_ENABLED_KEY, APP_ENABLED_DEFAULT_VALUE)
var musicVersionPref by managerStringPreference(APP_MUSIC_VERSION_KEY, APP_VERSION_DEFAULT_VALUE)

View File

@ -1,7 +0,0 @@
package com.vanced.manager.core.util
enum class Variant {
Root, Nonroot
}

View File

@ -0,0 +1,50 @@
package com.vanced.manager.datasource
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.os.Build
interface PkgInfoDatasource {
fun getVersionCode(packageName: String): Int?
fun getVersionName(packageName: String): String?
}
class PkgInfoDatasourceImpl(
private val packageManager: PackageManager
) : PkgInfoDatasource {
private companion object {
const val FLAG_NOTHING = 0
const val VERSION_IGNORE_MAJOR = 0xFFFFFFFF
}
@SuppressLint("WrongConstant")
override fun getVersionCode(packageName: String): Int? {
return try {
val packageInfo = packageManager.getPackageInfo(packageName, FLAG_NOTHING)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
packageInfo.longVersionCode.and(VERSION_IGNORE_MAJOR).toInt()
} else {
@Suppress("DEPRECATION")
packageInfo.versionCode
}
} catch (e: PackageManager.NameNotFoundException) {
null
}
}
@Suppress("DEPRECATION")
@SuppressLint("WrongConstant")
override fun getVersionName(packageName: String): String? {
return try {
packageManager
.getPackageInfo(packageName, FLAG_NOTHING)
.versionName
} catch (e: PackageManager.NameNotFoundException) {
null
}
}
}

View File

@ -0,0 +1,68 @@
package com.vanced.manager.datasource
import android.content.SharedPreferences
interface PreferenceDatasource {
var managerUseCustomTabs: Boolean
var managerMode: String
var managerTheme: String
}
class PreferenceDatasourceImpl(
private val sharedPreferences: SharedPreferences
) : PreferenceDatasource {
override var managerUseCustomTabs: Boolean
get() = getBoolean(PreferenceData.MANAGER_USE_CUSTOM_TABS_KEY, PreferenceData.MANAGER_USE_CUSTOM_TABS_DEFAULT_VALUE)
set(value) {
putBoolean(PreferenceData.MANAGER_USE_CUSTOM_TABS_KEY, value)
}
override var managerMode: String
get() = getString(PreferenceData.MANAGER_MODE_KEY, PreferenceData.MANAGER_MODE_DEFAULT_VALUE)
set(value) {
putString(PreferenceData.MANAGER_MODE_KEY, value)
}
override var managerTheme: String
get() = getString(PreferenceData.MANAGER_THEME_KEY, PreferenceData.MANAGER_THEME_DEFAULT_VALUE)
set(value) {
putString(PreferenceData.MANAGER_THEME_KEY, value)
}
private fun getString(key: String, defaultValue: String): String {
return sharedPreferences.getString(key, defaultValue) ?: defaultValue
}
private fun getBoolean(key: String, defaultValue: Boolean): Boolean {
return sharedPreferences.getBoolean(key, defaultValue)
}
private fun putString(key: String, value: String) {
sharedPreferences.edit().putString(key, value).apply()
}
private fun putBoolean(key: String, value: Boolean) {
sharedPreferences.edit().putBoolean(key, value).apply()
}
}
object PreferenceData {
const val MANAGER_USE_CUSTOM_TABS_KEY = "manager_behaviour_use_custom_tabs"
const val MANAGER_USE_CUSTOM_TABS_DEFAULT_VALUE = true
const val MANAGER_MODE_KEY = "manager_behaviour_mode"
const val MANAGER_MODE_VALUE_ROOT = "root"
const val MANAGER_MODE_VALUE_NONROOT= "nonroot"
const val MANAGER_MODE_DEFAULT_VALUE = MANAGER_MODE_VALUE_NONROOT
const val MANAGER_THEME_KEY = "manager_appearance_theme"
const val MANAGER_THEME_VALUE_LIGHT = "light"
const val MANAGER_THEME_VALUE_DARK = "dark"
const val MANAGER_THEME_VALUE_SYSTEM_DEFAULT = "system_default"
const val MANAGER_THEME_DEFAULT_VALUE = MANAGER_THEME_VALUE_SYSTEM_DEFAULT
}

View File

@ -0,0 +1,13 @@
package com.vanced.manager.di
import androidx.browser.customtabs.CustomTabsIntent
import org.koin.dsl.module
val customTabsModule = module {
fun provideChromeCustomTabs(): CustomTabsIntent {
return CustomTabsIntent.Builder()
.build()
}
single { provideChromeCustomTabs() }
}

View File

@ -0,0 +1,31 @@
package com.vanced.manager.di
import android.content.Context
import com.vanced.manager.datasource.PkgInfoDatasource
import com.vanced.manager.datasource.PkgInfoDatasourceImpl
import com.vanced.manager.datasource.PreferenceDatasource
import com.vanced.manager.datasource.PreferenceDatasourceImpl
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
val datasourceModule = module {
fun providePkgInfoDatasource(
context: Context
): PkgInfoDatasource {
return PkgInfoDatasourceImpl(
packageManager = context.packageManager
)
}
fun providePreferenceDatasource(
context: Context
): PreferenceDatasource {
return PreferenceDatasourceImpl(
sharedPreferences = context.getSharedPreferences("manager_settings", Context.MODE_PRIVATE)
)
}
single { providePkgInfoDatasource(androidContext()) }
single { providePreferenceDatasource(androidContext()) }
}

View File

@ -1,22 +0,0 @@
package com.vanced.manager.di
import android.content.Context
import com.vanced.manager.domain.datasource.PackageInformationDataSource
import com.vanced.manager.network.model.AppDtoMapper
import com.vanced.manager.network.model.DataDtoMapper
import org.koin.dsl.module
val mapperModule = module {
fun provideAppMapper(
packageInformationDataSource: PackageInformationDataSource,
context: Context,
) = AppDtoMapper(packageInformationDataSource, context)
fun provideJsonMapper(
appDtoMapper: AppDtoMapper
) = DataDtoMapper(appDtoMapper)
single { provideAppMapper(get(), get()) }
single { provideJsonMapper(get()) }
}

View File

@ -1,26 +0,0 @@
package com.vanced.manager.di
import android.content.Context
import com.vanced.manager.domain.datasource.PackageInformationDataSource
import com.vanced.manager.domain.datasource.PackageInformationDataSourceImpl
import com.vanced.manager.domain.pkg.PkgManager
import com.vanced.manager.domain.pkg.PkgManagerImpl
import org.koin.dsl.module
val packageManagerModule = module {
fun providePackageManager(
context: Context
): PkgManager =
PkgManagerImpl(
packageManager = context.packageManager
)
fun providePackageInformationDataSource(
pkgManager: PkgManager
): PackageInformationDataSource =
PackageInformationDataSourceImpl(pkgManager)
single { providePackageManager(get()) }
single { providePackageInformationDataSource(get()) }
}

View File

@ -1,13 +0,0 @@
package com.vanced.manager.di
import android.content.Context
import org.koin.dsl.module
val preferenceModule = module {
fun provideDatastore(
context: Context
) = context.getSharedPreferences("manager_settings", Context.MODE_PRIVATE)
single { provideDatastore(get()) }
}

View File

@ -1,25 +1,31 @@
package com.vanced.manager.di
import com.vanced.manager.network.DataService
import com.vanced.manager.network.model.DataDtoMapper
import com.vanced.manager.repository.DataRepository
import com.vanced.manager.repository.MainRepository
import com.vanced.manager.repository.MirrorRepository
import org.koin.core.qualifier.named
import com.vanced.manager.datasource.PkgInfoDatasource
import com.vanced.manager.datasource.PreferenceDatasource
import com.vanced.manager.network.GithubService
import com.vanced.manager.repository.*
import org.koin.dsl.module
val repositoryModule = module {
fun provideMainRepository(
dataService: DataService,
dataDtoMapper: DataDtoMapper
) = MainRepository(dataService, dataDtoMapper)
fun provideGithubRepository(
githubService: GithubService,
pkgInfoDatasource: PkgInfoDatasource
): AppRepository {
return AppRepositoryImpl(
githubService = githubService,
pkgInfoDatasource = pkgInfoDatasource
)
}
fun provideMirrorRepository(
dataService: DataService,
dataDtoMapper: DataDtoMapper
) = MirrorRepository(dataService, dataDtoMapper)
fun providePreferenceRepository(
preferenceDatasource: PreferenceDatasource
): PreferenceRepository {
return PreferenceRepositoryImpl(
preferenceDatasource = preferenceDatasource
)
}
single { provideMainRepository(get(named("main")), get()) }
single { provideMirrorRepository(get(named("mirror")), get()) }
single { provideGithubRepository(get(), get()) }
single { providePreferenceRepository(get()) }
}

View File

@ -1,35 +1,31 @@
package com.vanced.manager.di
import com.google.gson.GsonBuilder
import com.vanced.manager.network.DataService
import com.vanced.manager.network.util.BASE_GITHUB
import com.vanced.manager.network.util.BASE_MIRROR
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.vanced.manager.network.GithubService
import com.vanced.manager.network.util.GITHUB_API_BASE
import kotlinx.serialization.json.Json
import okhttp3.MediaType
import okhttp3.OkHttpClient
import org.koin.core.qualifier.named
import org.koin.dsl.module
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.create
private val json = Json {
ignoreUnknownKeys = true
}
val serviceModule = module {
fun provideMainService(
fun provideGithubService(
okHttpClient: OkHttpClient
) = Retrofit.Builder()
.baseUrl(BASE_GITHUB)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.client(okHttpClient)
.build()
.create(DataService::class.java)
): GithubService {
return Retrofit.Builder()
.baseUrl(GITHUB_API_BASE)
.addConverterFactory(json.asConverterFactory(MediaType.get("application/json")))
.client(okHttpClient)
.build()
.create()
}
fun provideMirrorService(
okHttpClient: OkHttpClient
) = Retrofit.Builder()
.baseUrl(BASE_MIRROR)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.client(okHttpClient)
.build()
.create(DataService::class.java)
single(named("main")) { provideMainService(get()) }
single(named("mirror")) { provideMirrorService(get()) }
single { provideGithubService(get()) }
}

View File

@ -1,16 +1,14 @@
package com.vanced.manager.di
import android.app.Application
import android.content.SharedPreferences
import com.vanced.manager.core.downloader.impl.MicrogDownloader
import com.vanced.manager.core.downloader.impl.MusicDownloader
import com.vanced.manager.core.downloader.impl.VancedDownloader
import com.vanced.manager.core.installer.impl.MicrogInstaller
import com.vanced.manager.core.installer.impl.MusicInstaller
import com.vanced.manager.core.installer.impl.VancedInstaller
import com.vanced.manager.repository.DataRepository
import com.vanced.manager.repository.MainRepository
import com.vanced.manager.repository.MirrorRepository
import com.vanced.manager.repository.AppRepository
import com.vanced.manager.repository.PreferenceRepository
import com.vanced.manager.ui.viewmodel.ConfigurationViewModel
import com.vanced.manager.ui.viewmodel.InstallViewModel
import com.vanced.manager.ui.viewmodel.MainViewModel
@ -22,11 +20,16 @@ import org.koin.dsl.module
val viewModelModule = module {
fun provideMainViewModel(
mainRepository: MainRepository,
mirrorRepository: MirrorRepository,
preferences: SharedPreferences,
appRepository: AppRepository,
preferenceRepository: PreferenceRepository,
app: Application,
) = MainViewModel(mainRepository, mirrorRepository, preferences, app)
): MainViewModel {
return MainViewModel(
appRepository = appRepository,
preferenceRepository = preferenceRepository,
app = app
)
}
fun provideInstallViewModel(
vancedDownloader: VancedDownloader,
@ -42,12 +45,16 @@ val viewModelModule = module {
return ConfigurationViewModel()
}
fun provideSettingsViewModel(): SettingsViewModel {
return SettingsViewModel()
fun provideSettingsViewModel(
preferenceRepository: PreferenceRepository
): SettingsViewModel {
return SettingsViewModel(
preferenceRepository = preferenceRepository
)
}
viewModel { provideMainViewModel(get(), get(), get(), androidApplication()) }
viewModel { provideMainViewModel(get(), get(), androidApplication()) }
viewModel { provideInstallViewModel(get(), get(), get(), get(), get(), get()) }
viewModel { provideConfigurationViewModel() }
viewModel { provideSettingsViewModel() }
viewModel { provideSettingsViewModel(get()) }
}

View File

@ -1,36 +0,0 @@
package com.vanced.manager.domain.datasource
import android.content.pm.PackageManager
import com.vanced.manager.domain.pkg.PkgManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
interface PackageInformationDataSource {
suspend fun getVersionCode(packageName: String): Int?
suspend fun getVersionName(packageName: String): String?
}
class PackageInformationDataSourceImpl(
private val pkgManager: PkgManager
) : PackageInformationDataSource {
override suspend fun getVersionCode(packageName: String): Int? =
withContext(Dispatchers.IO) {
try {
pkgManager.getVersionCode(packageName)
} catch (exception: PackageManager.NameNotFoundException) {
null
}
}
override suspend fun getVersionName(packageName: String): String? =
withContext(Dispatchers.IO) {
try {
pkgManager.getVersionName(packageName)
} catch (exception: PackageManager.NameNotFoundException) {
null
}
}
}

View File

@ -1,18 +1,58 @@
package com.vanced.manager.domain.model
import androidx.annotation.DrawableRes
import com.vanced.manager.R
data class App(
val name: String,
val remoteVersion: String,
val remoteVersionCode: Int,
val installedVersion: String?,
val installedVersionCode: Int?,
val iconUrl: String?,
val appStatus: AppStatus,
val packageName: String,
@DrawableRes val iconResId: Int,
val changelog: String,
val url: String?,
val versions: List<String>?,
val themes: List<String>?,
val languages: List<String>?,
val installationOptions: List<InstallationOption>?
)
val remoteVersionCode: Int,
val remoteVersionName: String,
val installedVersionCode: Int?,
val installedVersionName: String?,
val packageName: String,
val launchActivity: String,
val state: AppState,
val app: AppType
)
object AppData {
const val NAME_VANCED_YOUTUBE = "YouTube Vanced"
const val NAME_VANCED_YOUTUBE_MUSIC = "YouTube Vanced Music"
const val NAME_VANCED_MICROG = "Vanced microG"
const val NAME_VANCED_MANAGER = "Vanced Manager"
const val ICON_VANCED_YOUTUBE = R.drawable.ic_vanced
const val ICON_VANCED_YOUTUBE_MUSIC = R.drawable.ic_music
const val ICON_VANCED_MICROG = R.drawable.ic_microg
const val ICON_VANCED_MANAGER = R.drawable.ic_manager
const val PACKAGE_VANCED_YOUTUBE = "com.vanced.android.youtube"
const val PACKAGE_VANCED_YOUTUBE_MUSIC = "com.vanced.android.youtube.apps.music"
const val PACKAGE_VANCED_MICROG = "com.mgoogle.android.gms"
const val PACKAGE_VANCED_MANAGER = "com.vanced.manager"
const val PACKAGE_ROOT_VANCED_YOUTUBE = "com.google.android.youtube"
const val PACKAGE_ROOT_VANCED_YOUTUBE_MUSIC = "com.google.android.youtube.apps.music"
const val LAUNCH_ACTIVITY_VANCED_YOUTUBE = "com.google.android.youtube.HomeActivity"
const val LAUNCH_ACTIVITY_VANCED_YOUTUBE_MUSIC = "com.google.android.apps.youtube.music.activities.MusicActivity"
const val LAUNCH_ACTIVITY_VANCED_MICROG = "org.microg.gms.ui.SettingsActivity"
const val LAUNCH_ACTIVITY_VANCED_MANAGER = ""
}
enum class AppType {
VANCED_YOUTUBE,
VANCED_YOUTUBE_MUSIC,
VANCED_MICROG,
VANCED_MANAGER,
}
enum class AppState {
NOT_INSTALLED,
INSTALLED,
NEEDS_UPDATE
}

View File

@ -1,9 +0,0 @@
package com.vanced.manager.domain.model
enum class AppStatus {
Install,
Reinstall,
Update,
}

View File

@ -1,6 +0,0 @@
package com.vanced.manager.domain.model
data class Data(
val manager: App,
val apps: List<App>,
)

View File

@ -1,49 +0,0 @@
package com.vanced.manager.domain.pkg
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.os.Build
interface PkgManager {
@Throws(PackageManager.NameNotFoundException::class)
suspend fun getVersionCode(packageName: String): Int
@Throws(PackageManager.NameNotFoundException::class)
suspend fun getVersionName(packageName: String): String
}
class PkgManagerImpl(
private val packageManager: PackageManager
) : PkgManager {
private companion object {
const val PACKAGE_FLAG_ALL_OFF = 0
const val MAJOR_IGNORE = 0xFFFFFFFF
}
@SuppressLint("WrongConstant")
@Suppress("DEPRECATION")
@Throws(PackageManager.NameNotFoundException::class)
override suspend fun getVersionCode(
packageName: String
) = with(packageManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
getPackageInfo(packageName, PACKAGE_FLAG_ALL_OFF)
.longVersionCode
.and(MAJOR_IGNORE)
.toInt()
} else {
getPackageInfo(packageName, PACKAGE_FLAG_ALL_OFF)
.versionCode
}
}
@SuppressLint("WrongConstant")
@Throws(PackageManager.NameNotFoundException::class)
override suspend fun getVersionName(
packageName: String
): String = packageManager
.getPackageInfo(packageName, PACKAGE_FLAG_ALL_OFF)
.versionName
}

View File

@ -1,7 +0,0 @@
package com.vanced.manager.domain.util
interface EntityMapper<T, Model> {
suspend fun mapToModel(entity: T): Model
}

View File

@ -1,11 +0,0 @@
package com.vanced.manager.network
import com.vanced.manager.network.model.DataDto
import retrofit2.http.GET
interface DataService {
@GET("latest.json")
suspend fun get(): DataDto
}

View File

@ -0,0 +1,22 @@
package com.vanced.manager.network
import com.vanced.manager.network.dto.GithubReleaseDto
import retrofit2.http.GET
private const val REPOS_VANCED = "repos/YTVanced"
interface GithubService {
@GET("$REPOS_VANCED/Vanced/releases/latest")
suspend fun getVancedYoutubeRelease(): GithubReleaseDto
@GET("$REPOS_VANCED/VancedMusic/releases/latest")
suspend fun getVancedYoutubeMusicRelease(): GithubReleaseDto
@GET("$REPOS_VANCED/VancedMicrog/releases/latest")
suspend fun getVancedMicrogRelease(): GithubReleaseDto
@GET("$REPOS_VANCED/VancedManager/releases/latest")
suspend fun getVancedManagerRelease(): GithubReleaseDto
}

View File

@ -0,0 +1,25 @@
package com.vanced.manager.network.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GithubReleaseDto(
@SerialName("tag_name")
val tagName: String,
@SerialName("body")
val body: String,
@SerialName("assets")
val assets: List<GithubReleaseAssetDto>
)
@Serializable
data class GithubReleaseAssetDto(
@SerialName("name")
val name: String,
@SerialName("browser_download_url")
val browserDownloadUrl: String
)

View File

@ -1,16 +0,0 @@
package com.vanced.manager.network.model
import com.google.gson.annotations.SerializedName
data class AppDto(
@SerializedName("name") val name: String,
@SerializedName("version") val version: String,
@SerializedName("version_code") val versionCode: Int,
@SerializedName("changelog") val changelog: String,
@SerializedName("icon_url") val iconUrl: String,
@SerializedName("package_name") val packageName: String,
@SerializedName("url") val url: String? = null,
@SerializedName("versions") val versions: List<String>? = null,
@SerializedName("themes") val themes: List<String>? = null,
@SerializedName("langs") val languages: List<String>? = null,
)

View File

@ -1,156 +0,0 @@
package com.vanced.manager.network.model
import android.content.Context
import com.vanced.manager.R
import com.vanced.manager.core.preferences.holder.musicVersionPref
import com.vanced.manager.core.preferences.holder.vancedLanguagesPref
import com.vanced.manager.core.preferences.holder.vancedThemePref
import com.vanced.manager.core.preferences.holder.vancedVersionPref
import com.vanced.manager.domain.datasource.PackageInformationDataSource
import com.vanced.manager.domain.model.App
import com.vanced.manager.domain.model.AppStatus
import com.vanced.manager.domain.model.InstallationOption
import com.vanced.manager.domain.model.InstallationOptionItem
import com.vanced.manager.domain.util.EntityMapper
import com.vanced.manager.network.util.MUSIC_NAME
import com.vanced.manager.network.util.VANCED_NAME
import java.util.*
class AppDtoMapper(
private val pkgInfoDataSource: PackageInformationDataSource,
private val context: Context
) : EntityMapper<AppDto, App> {
private val latestVersionRadioButton =
InstallationOptionItem(
displayText = { context.getString(R.string.app_version_dialog_option_latest) },
key = "latest"
)
override suspend fun mapToModel(
entity: AppDto
) = with(entity) {
val localVersionCode = pkgInfoDataSource.getVersionCode(packageName)
App(
name = name,
remoteVersion = version,
remoteVersionCode = versionCode,
installedVersion = pkgInfoDataSource.getVersionName(packageName),
installedVersionCode = localVersionCode,
appStatus = compareVersionCodes(versionCode, localVersionCode),
packageName = packageName,
iconUrl = iconUrl,
changelog = changelog,
url = url,
versions = versions,
themes = themes,
languages = languages,
installationOptions = getInstallationOptions(name, themes, versions, languages)
)
}
private fun compareVersionCodes(
remote: Int?,
local: Int?
) = if (local != null && remote != null) {
when {
remote > local -> AppStatus.Update
remote <= local -> AppStatus.Reinstall
else -> AppStatus.Install
}
} else {
AppStatus.Install
}
private fun getInstallationOptions(
appName: String,
appThemes: List<String>?,
appVersions: List<String>?,
appLanguages: List<String>?,
) : List<InstallationOption>? = when (appName) {
VANCED_NAME -> buildList {
if (appThemes != null) {
add(
InstallationOption.SingleSelect(
titleId = R.string.app_installation_options_theme,
getOption = { vancedThemePref },
setOption = {
vancedThemePref = it
},
items = appThemes.map { theme ->
InstallationOptionItem(
displayText = {
theme.replaceFirstChar {
it.titlecase(Locale.getDefault())
}
},
key = theme
)
},
)
)
}
if (appVersions != null) {
add(
InstallationOption.SingleSelect(
titleId = R.string.app_installation_options_version,
getOption = { vancedVersionPref },
setOption = {
vancedVersionPref = it
},
items = (appVersions.map { version ->
InstallationOptionItem(
displayText = { "v$version" },
key = version
)
} + latestVersionRadioButton).reversed(),
)
)
}
if (appLanguages != null) {
add(
InstallationOption.MultiSelect(
titleId = R.string.app_installation_options_language,
getOption = { vancedLanguagesPref },
addOption = {
vancedLanguagesPref = vancedLanguagesPref + it
},
removeOption = {
vancedLanguagesPref = vancedLanguagesPref - it
},
items = appLanguages.map { language ->
InstallationOptionItem(
displayText = {
val locale = Locale(it)
locale.getDisplayName(locale)
},
key = language
)
},
)
)
}
}
MUSIC_NAME -> buildList {
if (appVersions != null) {
add(
InstallationOption.SingleSelect(
titleId = R.string.app_installation_options_version,
getOption = { musicVersionPref },
setOption = {
musicVersionPref = it
},
items = (appVersions.map { version ->
InstallationOptionItem(
displayText = { "v$version" },
key = version
)
} + latestVersionRadioButton).reversed(),
)
)
}
}
else -> null
}
}

View File

@ -1,13 +0,0 @@
package com.vanced.manager.network.model
import com.google.gson.annotations.SerializedName
data class DataDto(
@SerializedName("manager") val manager: AppDto,
@SerializedName("apps") val apps: DataAppDto
)
data class DataAppDto(
@SerializedName("nonroot") val nonroot: List<AppDto>,
@SerializedName("root") val root: List<AppDto>,
)

View File

@ -1,26 +0,0 @@
package com.vanced.manager.network.model
import com.vanced.manager.core.preferences.holder.managerVariantPref
import com.vanced.manager.domain.model.Data
import com.vanced.manager.domain.util.EntityMapper
class DataDtoMapper(
private val appDtoMapper: AppDtoMapper
) : EntityMapper<DataDto, Data> {
override suspend fun mapToModel(
entity: DataDto
) = with(entity) {
Data(
manager = appDtoMapper.mapToModel(manager),
apps =
if (managerVariantPref == "root") {
apps.root
} else {
apps.nonroot
}.map { appDto ->
appDtoMapper.mapToModel(appDto)
}
)
}
}

View File

@ -1,8 +1,8 @@
package com.vanced.manager.network.util
const val BASE = "https://api.vancedapp.com/api/v1/"
const val BASE_MIRROR = "https://x1nto.github.io/VancedFiles/api/v2/" //TODO
const val BASE_GITHUB = "https://x1nto.github.io/VancedFiles/api/v2/"
const val GITHUB_API_BASE = "https://api.github.com/"
const val VANCED_NAME = "YouTube Vanced"
const val MUSIC_NAME = "YouTube Vanced Music"

View File

@ -0,0 +1,172 @@
package com.vanced.manager.repository
import com.vanced.manager.datasource.PkgInfoDatasource
import com.vanced.manager.domain.model.AppData
import com.vanced.manager.domain.model.AppState
import com.vanced.manager.domain.model.AppType
import com.vanced.manager.domain.model.App
import com.vanced.manager.network.GithubService
import com.vanced.manager.network.dto.GithubReleaseDto
interface AppRepository {
suspend fun getVancedYoutubeNonroot(): App
suspend fun getVancedYoutubeRoot(): App
suspend fun getVancedYoutubeMusicNonroot(): App
suspend fun getVancedYoutubeMusicRoot(): App
suspend fun getVancedMicrog(): App
suspend fun getVancedManager(): App
}
class AppRepositoryImpl(
private val githubService: GithubService,
private val pkgInfoDatasource: PkgInfoDatasource,
) : AppRepository {
override suspend fun getVancedYoutubeNonroot(): App {
val githubRelease = githubService.getVancedYoutubeRelease()
val remoteVersionCode = githubRelease.getVersionCode()
val remoteVersionName = githubRelease.getVersionName()
val installedVersionCode = pkgInfoDatasource.getVersionCode(AppData.PACKAGE_VANCED_YOUTUBE)
val installedVersionName = pkgInfoDatasource.getVersionName(AppData.PACKAGE_VANCED_YOUTUBE)
return App(
name = AppData.NAME_VANCED_YOUTUBE,
iconResId = AppData.ICON_VANCED_YOUTUBE,
changelog = githubRelease.body,
remoteVersionCode = remoteVersionCode,
remoteVersionName = remoteVersionName,
installedVersionCode = installedVersionCode,
installedVersionName = installedVersionName,
packageName = AppData.PACKAGE_VANCED_YOUTUBE,
launchActivity = AppData.LAUNCH_ACTIVITY_VANCED_YOUTUBE,
state = getNoonrotAppState(installedVersionCode, remoteVersionCode),
app = AppType.VANCED_YOUTUBE,
)
}
override suspend fun getVancedYoutubeRoot(): App {
val githubRelease = githubService.getVancedYoutubeRelease()
val remoteVersionCode = githubRelease.getVersionCode()
val remoteVersionName = githubRelease.getVersionName()
val installedVersionCode = pkgInfoDatasource.getVersionCode(AppData.PACKAGE_ROOT_VANCED_YOUTUBE)
val installedVersionName = pkgInfoDatasource.getVersionName(AppData.PACKAGE_ROOT_VANCED_YOUTUBE)
return App(
name = AppData.NAME_VANCED_YOUTUBE,
iconResId = AppData.ICON_VANCED_YOUTUBE,
changelog = githubRelease.body,
remoteVersionCode = remoteVersionCode,
remoteVersionName = remoteVersionName,
installedVersionCode = installedVersionCode,
installedVersionName = installedVersionName,
packageName = AppData.PACKAGE_VANCED_YOUTUBE,
launchActivity = AppData.LAUNCH_ACTIVITY_VANCED_YOUTUBE,
state = getNoonrotAppState(installedVersionCode, remoteVersionCode),
app = AppType.VANCED_YOUTUBE,
)
}
override suspend fun getVancedYoutubeMusicNonroot(): App {
val githubRelease = githubService.getVancedYoutubeMusicRelease()
val remoteVersionCode = githubRelease.getVersionCode()
val remoteVersionName = githubRelease.getVersionName()
val installedVersionCode = pkgInfoDatasource.getVersionCode(AppData.PACKAGE_VANCED_YOUTUBE_MUSIC)
val installedVersionName = pkgInfoDatasource.getVersionName(AppData.PACKAGE_VANCED_YOUTUBE_MUSIC)
return App(
name = AppData.NAME_VANCED_YOUTUBE_MUSIC,
iconResId = AppData.ICON_VANCED_YOUTUBE_MUSIC,
changelog = githubRelease.body,
remoteVersionCode = remoteVersionCode,
remoteVersionName = remoteVersionName,
installedVersionCode = installedVersionCode,
installedVersionName = installedVersionName,
packageName = AppData.PACKAGE_VANCED_YOUTUBE_MUSIC,
launchActivity = AppData.LAUNCH_ACTIVITY_VANCED_YOUTUBE_MUSIC,
state = getNoonrotAppState(installedVersionCode, remoteVersionCode),
app = AppType.VANCED_YOUTUBE_MUSIC,
)
}
override suspend fun getVancedYoutubeMusicRoot(): App {
val githubRelease = githubService.getVancedYoutubeMusicRelease()
val remoteVersionCode = githubRelease.getVersionCode()
val remoteVersionName = githubRelease.getVersionName()
val installedVersionCode = pkgInfoDatasource.getVersionCode(AppData.PACKAGE_ROOT_VANCED_YOUTUBE_MUSIC)
val installedVersionName = pkgInfoDatasource.getVersionName(AppData.PACKAGE_ROOT_VANCED_YOUTUBE_MUSIC)
return App(
name = AppData.NAME_VANCED_YOUTUBE_MUSIC,
iconResId = AppData.ICON_VANCED_YOUTUBE_MUSIC,
changelog = githubRelease.body,
remoteVersionCode = remoteVersionCode,
remoteVersionName = remoteVersionName,
installedVersionCode = installedVersionCode,
installedVersionName = installedVersionName,
packageName = AppData.PACKAGE_VANCED_YOUTUBE_MUSIC,
launchActivity = AppData.LAUNCH_ACTIVITY_VANCED_YOUTUBE_MUSIC,
state = getNoonrotAppState(installedVersionCode, remoteVersionCode),
app = AppType.VANCED_YOUTUBE_MUSIC,
)
}
override suspend fun getVancedMicrog(): App {
val githubRelease = githubService.getVancedMicrogRelease()
val remoteVersionCode = githubRelease.getVersionCode()
val remoteVersionName = githubRelease.getVersionName()
val installedVersionCode = pkgInfoDatasource.getVersionCode(AppData.PACKAGE_VANCED_MICROG)
val installedVersionName = pkgInfoDatasource.getVersionName(AppData.PACKAGE_VANCED_MICROG)
return App(
name = AppData.NAME_VANCED_MICROG,
iconResId = AppData.ICON_VANCED_MICROG,
changelog = githubRelease.body,
remoteVersionCode = remoteVersionCode,
remoteVersionName = remoteVersionName,
installedVersionCode = installedVersionCode,
installedVersionName = installedVersionName,
packageName = AppData.PACKAGE_VANCED_MICROG,
launchActivity = AppData.LAUNCH_ACTIVITY_VANCED_MICROG,
state = getNoonrotAppState(installedVersionCode, remoteVersionCode),
app = AppType.VANCED_MICROG,
)
}
override suspend fun getVancedManager(): App {
val githubRelease = githubService.getVancedManagerRelease()
val remoteVersionCode = githubRelease.getVersionCode()
val remoteVersionName = githubRelease.getVersionName()
val installedVersionCode = pkgInfoDatasource.getVersionCode(AppData.PACKAGE_VANCED_MANAGER)
val installedVersionName = pkgInfoDatasource.getVersionName(AppData.PACKAGE_VANCED_MANAGER)
return App(
name = AppData.NAME_VANCED_MANAGER,
iconResId = AppData.ICON_VANCED_MANAGER,
changelog = githubRelease.body,
remoteVersionCode = remoteVersionCode,
remoteVersionName = remoteVersionName,
installedVersionCode = installedVersionCode,
installedVersionName = installedVersionName,
packageName = AppData.PACKAGE_VANCED_MANAGER,
launchActivity = AppData.LAUNCH_ACTIVITY_VANCED_MANAGER,
state = getNoonrotAppState(installedVersionCode, remoteVersionCode),
app = AppType.VANCED_MANAGER,
)
}
private fun getNoonrotAppState(
installedVersionCode: Int?,
remoteVersionCode: Int
): AppState {
return when {
installedVersionCode == null -> AppState.NOT_INSTALLED
installedVersionCode < remoteVersionCode -> AppState.NEEDS_UPDATE
installedVersionCode >= remoteVersionCode -> AppState.INSTALLED
else -> AppState.NOT_INSTALLED
}
}
private fun GithubReleaseDto.getVersionCode() = tagName.substringAfter("-").toInt()
private fun GithubReleaseDto.getVersionName() = tagName.substringBefore("-")
}

View File

@ -1,9 +0,0 @@
package com.vanced.manager.repository
import com.vanced.manager.domain.model.Data
interface DataRepository {
suspend fun fetch(): Data
}

View File

@ -1,14 +0,0 @@
package com.vanced.manager.repository
import com.vanced.manager.network.DataService
import com.vanced.manager.network.model.DataDtoMapper
class MainRepository(
private val mainService: DataService,
private val mapper: DataDtoMapper
) : DataRepository {
override suspend fun fetch() =
mapper.mapToModel(mainService.get())
}

View File

@ -1,14 +0,0 @@
package com.vanced.manager.repository
import com.vanced.manager.network.DataService
import com.vanced.manager.network.model.DataDtoMapper
class MirrorRepository(
private val mirrorService: DataService,
private val mapper: DataDtoMapper
) : DataRepository {
override suspend fun fetch() =
mapper.mapToModel(mirrorService.get())
}

View File

@ -0,0 +1,77 @@
package com.vanced.manager.repository
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import com.vanced.manager.datasource.PreferenceData
import com.vanced.manager.datasource.PreferenceDatasource
interface PreferenceRepository {
var managerUseCustomTabs: Boolean
var managerMode: ManagerMode
var managerTheme: ManagerTheme
}
class PreferenceRepositoryImpl(
private val preferenceDatasource: PreferenceDatasource
) : PreferenceRepository {
override var managerUseCustomTabs: Boolean
get() = preferenceDatasource.managerUseCustomTabs
set(value) {
preferenceDatasource.managerUseCustomTabs = value
}
override var managerMode: ManagerMode
get() = ManagerMode.fromValue(preferenceDatasource.managerMode)
set(value) {
preferenceDatasource.managerMode = value.value
}
override var managerTheme: ManagerTheme
get() = ManagerTheme.fromValue(preferenceDatasource.managerTheme)
set(value) {
preferenceDatasource.managerTheme = value.value
}
}
enum class ManagerTheme(val value: String) {
LIGHT(PreferenceData.MANAGER_THEME_VALUE_LIGHT),
DARK(PreferenceData.MANAGER_THEME_VALUE_DARK),
SYSTEM_DEFAULT(PreferenceData.MANAGER_THEME_VALUE_SYSTEM_DEFAULT);
@Composable
fun isDark() = when (this) {
LIGHT -> false
DARK -> true
SYSTEM_DEFAULT -> isSystemInDarkTheme()
}
companion object {
fun fromValue(value: String?): ManagerTheme {
return values().find {
it.value == value
} ?: SYSTEM_DEFAULT
}
}
}
enum class ManagerMode(val value: String) {
ROOT(PreferenceData.MANAGER_MODE_VALUE_ROOT),
NONROOT(PreferenceData.MANAGER_MODE_VALUE_NONROOT);
val isRoot get() = this == ROOT
val isNonroot get() = this == NONROOT
companion object {
fun fromValue(value: String?): ManagerMode {
return when (value) {
"root" -> ROOT
else -> NONROOT
}
}
}
}

View File

@ -73,12 +73,15 @@ class MainActivity : ComponentActivity() {
when (val screen = backStack.last()) {
is Screen.Home -> {
HomeScreen(
viewModel = mainViewModel,
managerState = mainViewModel.appState,
onRefresh = {
mainViewModel.fetch()
},
onToolbarScreenSelected = {
backStack.push(it)
},
onAppDownloadClick = { appName, appVersions, installationOptions ->
if (installationOptions != null) {
onAppDownloadClick = { app ->
/*if (installationOptions != null) {
backStack.push(
Screen.Configuration(
appName,
@ -88,13 +91,13 @@ class MainActivity : ComponentActivity() {
)
} else {
backStack.push(Screen.Install(appName, appVersions))
}
}*/
},
onAppLaunchClick = { appName, packageName ->
mainViewModel.launchApp(appName, packageName)
onAppLaunchClick = { app ->
mainViewModel.launchApp(app.packageName, app.launchActivity)
},
onAppUninstallClick = { packageName ->
mainViewModel.uninstallApp(packageName)
onAppUninstallClick = { app ->
mainViewModel.uninstallApp(app.packageName)
}
)
}

View File

@ -15,36 +15,30 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import coil.compose.rememberImagePainter
import coil.request.CachePolicy
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import com.vanced.manager.R
import com.vanced.manager.core.util.socialMedia
import com.vanced.manager.core.util.sponsors
import com.vanced.manager.domain.model.App
import com.vanced.manager.domain.model.InstallationOption
import com.vanced.manager.ui.component.*
import com.vanced.manager.ui.resource.managerString
import com.vanced.manager.ui.util.Screen
import com.vanced.manager.ui.viewmodel.MainViewModel
import com.vanced.manager.ui.viewmodel.ManagerState
import com.vanced.manager.ui.widget.AppCard
import com.vanced.manager.ui.widget.AppCardPlaceholder
import com.vanced.manager.ui.widget.LinkCard
@Composable
fun HomeScreen(
viewModel: MainViewModel,
managerState: ManagerState,
onRefresh: () -> Unit,
onToolbarScreenSelected: (Screen) -> Unit,
onAppDownloadClick: (
appName: String,
appVersions: List<String>?,
installationOptions: List<InstallationOption>?
) -> Unit,
onAppUninstallClick: (appPackage: String) -> Unit,
onAppLaunchClick: (appName: String, appPackage: String) -> Unit,
onAppDownloadClick: (App) -> Unit,
onAppUninstallClick: (App) -> Unit,
onAppLaunchClick: (App) -> Unit,
) {
val refreshState =
rememberSwipeRefreshState(isRefreshing = viewModel.appState.isFetching)
rememberSwipeRefreshState(isRefreshing = managerState.isFetching)
var menuExpanded by remember { mutableStateOf(false) }
val dropdownScreens = remember { listOf(Screen.Settings, Screen.About) }
@ -68,24 +62,24 @@ fun HomeScreen(
.fillMaxSize()
.padding(paddingValues),
swipeRefreshState = refreshState,
onRefresh = { viewModel.fetch() }
onRefresh = onRefresh
) {
AnimatedContent(
modifier = Modifier.fillMaxSize(),
targetState = viewModel.appState,
targetState = managerState,
transitionSpec = {
scaleIn(initialScale = 0.9f) + fadeIn() with
scaleOut(targetScale = 0.9f) + fadeOut()
}
) { animatedAppState ->
when (animatedAppState) {
is StateFetching -> {
is ManagerState.Fetching -> {
HomeScreenLoading(
modifier = Modifier.fillMaxSize(),
appsCount = animatedAppState.placeholderAppsCount
)
}
is StateSuccess -> {
is ManagerState.Success -> {
HomeScreenLoaded(
modifier = Modifier.fillMaxSize(),
apps = animatedAppState.apps,
@ -94,7 +88,7 @@ fun HomeScreen(
onAppLaunchClick = onAppLaunchClick
)
}
is StateError -> {
is ManagerState.Error -> {
//TODO
}
}
@ -103,10 +97,6 @@ fun HomeScreen(
}
}
typealias StateFetching = MainViewModel.AppState.Fetching
typealias StateSuccess = MainViewModel.AppState.Success
typealias StateError = MainViewModel.AppState.Error
@Composable
private fun HomeScreenTopBar(
menuExpanded: Boolean,
@ -150,49 +140,34 @@ private fun HomeScreenTopBar(
private fun HomeScreenLoaded(
modifier: Modifier = Modifier,
apps: List<App>,
onAppDownloadClick: (
appName: String,
appVersions: List<String>?,
installationOptions: List<InstallationOption>?
) -> Unit,
onAppUninstallClick: (appPackage: String) -> Unit,
onAppLaunchClick: (appName: String, appPackage: String) -> Unit,
onAppDownloadClick: (App) -> Unit,
onAppUninstallClick: (App) -> Unit,
onAppLaunchClick: (App) -> Unit,
) {
HomeScreenBody(modifier = modifier) {
managerCategory(categoryName = {
managerString(R.string.home_category_apps)
}) {
items(apps) { app ->
val appIcon = rememberImagePainter(app.iconUrl) {
diskCachePolicy(CachePolicy.ENABLED)
}
val appIcon = painterResource(id = app.iconResId)
var showAppInfoDialog by remember { mutableStateOf(false) }
AppCard(
modifier = Modifier
.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(),
appName = app.name,
appIcon = appIcon,
appInstalledVersion = app.installedVersion,
appRemoteVersion = app.remoteVersion,
appInstalledVersion = app.installedVersionName,
appRemoteVersion = app.remoteVersionName,
appState = app.state,
onAppDownloadClick = {
onAppDownloadClick(
app.name,
app.versions,
app.installationOptions
)
onAppDownloadClick(app)
},
onAppUninstallClick = {
onAppUninstallClick(
app.packageName
)
onAppUninstallClick(app)
},
onAppLaunchClick = {
onAppLaunchClick(
app.name,
app.packageName,
)
onAppLaunchClick(app)
},
onAppInfoClick = {
showAppInfoDialog = true

View File

@ -11,12 +11,11 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.vanced.manager.R
import com.vanced.manager.core.preferences.holder.managerThemePref
import com.vanced.manager.core.preferences.holder.managerVariantPref
import com.vanced.manager.core.util.isMagiskInstalled
import com.vanced.manager.repository.ManagerMode
import com.vanced.manager.repository.ManagerTheme
import com.vanced.manager.ui.component.*
import com.vanced.manager.ui.resource.managerString
import com.vanced.manager.ui.theme.ManagerTheme
import com.vanced.manager.ui.util.Screen
import com.vanced.manager.ui.viewmodel.SettingsViewModel
import org.koin.androidx.compose.viewModel
@ -60,13 +59,13 @@ fun SettingsScreen(
preferenceDescription = stringResource(id = R.string.settings_preference_use_custom_tabs_summary),
isChecked = viewModel.managerUseCustomTabs,
onCheckedChange = {
viewModel.managerUseCustomTabs = it
viewModel.saveManagerUseCustomTabs(it)
}
)
}
item {
var showDialog by remember { mutableStateOf(false) }
var selectedMode by remember { mutableStateOf(EntryValue(viewModel.managerMode)) }
var selectedMode by remember { mutableStateOf(EntryValue(viewModel.managerMode.value)) }
ManagerSingleSelectDialogPreference(
preferenceTitle = managerString(
stringId = R.string.settings_preference_variant_title
@ -83,7 +82,7 @@ fun SettingsScreen(
},
onDismissRequest = {
showDialog = false
selectedMode = EntryValue(managerVariantPref)
selectedMode = EntryValue(viewModel.managerMode.value)
},
onEntrySelect = {
if (it.value == "root" && !isMagiskInstalled)
@ -92,7 +91,7 @@ fun SettingsScreen(
selectedMode = it
},
onSave = {
viewModel.managerMode = selectedMode.value
viewModel.saveManagerMode(ManagerMode.fromValue(selectedMode.value))
showDialog = false
}
)
@ -103,36 +102,38 @@ fun SettingsScreen(
}) {
item {
var showDialog by remember { mutableStateOf(false) }
var selectedTheme by remember { mutableStateOf(EntryValue(managerThemePref)) }
var selectedTheme by remember { mutableStateOf(EntryValue(viewModel.managerTheme.value)) }
ManagerSingleSelectDialogPreference(
preferenceTitle = managerString(stringId = R.string.settings_preference_theme_title),
preferenceDescription = managerString(
stringId = viewModel.getThemeStringIdByValue(selectedTheme.value)
stringId = viewModel.getThemeStringId(
ManagerTheme.fromValue(selectedTheme.value)
)
),
showDialog = showDialog,
selected = selectedTheme,
entries = mapOf(
EntryText(managerString(viewModel.getThemeStringIdByValue(SettingsViewModel.THEME_LIGHT_VALUE))) to
EntryValue(SettingsViewModel.THEME_LIGHT_VALUE),
EntryText(managerString(viewModel.getThemeStringIdByValue(SettingsViewModel.THEME_DARK_VALUE))) to
EntryValue(SettingsViewModel.THEME_DARK_VALUE),
EntryText(managerString(viewModel.getThemeStringIdByValue(SettingsViewModel.THEME_SYSTEM_DEFAULT_VALUE))) to
EntryValue(SettingsViewModel.THEME_SYSTEM_DEFAULT_VALUE),
EntryText(managerString(viewModel.getThemeStringId(ManagerTheme.LIGHT)))
to EntryValue(ManagerTheme.LIGHT.value),
EntryText(managerString(viewModel.getThemeStringId(ManagerTheme.DARK)))
to EntryValue(ManagerTheme.DARK.value),
EntryText(managerString(viewModel.getThemeStringId(ManagerTheme.SYSTEM_DEFAULT)))
to EntryValue(ManagerTheme.SYSTEM_DEFAULT.value),
),
onClick = {
showDialog = true
},
onDismissRequest = {
showDialog = false
selectedTheme = EntryValue(viewModel.managerTheme)
selectedTheme = EntryValue(viewModel.managerTheme.value)
},
onEntrySelect = {
selectedTheme = it
},
onSave = {
viewModel.managerTheme = selectedTheme.value
showDialog = false
onThemeChange(ManagerTheme.fromKey(selectedTheme.value))
viewModel.saveManagerTheme(ManagerTheme.fromValue(selectedTheme.value))
onThemeChange(ManagerTheme.fromValue(selectedTheme.value))
}
)
}

View File

@ -8,7 +8,6 @@ import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
import com.vanced.manager.ui.viewmodel.SettingsViewModel
const val defAccentColor = 0xFF0477E1
@ -67,29 +66,6 @@ private val DarkThemeColors = darkColorScheme(
inverseSurface = md_theme_dark_inverseSurface,
)
enum class ManagerTheme {
LIGHT,
DARK,
SYSTEM_DEFAULT;
@Composable
fun isDark() = when (this) {
LIGHT -> false
DARK -> true
SYSTEM_DEFAULT -> isSystemInDarkTheme()
}
companion object {
fun fromKey(key: String?): ManagerTheme {
return when (key) {
SettingsViewModel.THEME_DARK_VALUE -> DARK
SettingsViewModel.THEME_LIGHT_VALUE -> LIGHT
else -> SYSTEM_DEFAULT
}
}
}
}
@Composable
inline fun apiDependantColorScheme(
dynamic: () -> ColorScheme,

View File

@ -1,95 +1,84 @@
package com.vanced.manager.ui.viewmodel
import android.app.Application
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Intent
import android.content.SharedPreferences
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.vanced.manager.core.installer.util.PM
import com.vanced.manager.core.preferences.holder.managerVariantPref
import com.vanced.manager.domain.model.App
import com.vanced.manager.network.util.MICROG_NAME
import com.vanced.manager.network.util.MUSIC_NAME
import com.vanced.manager.network.util.VANCED_NAME
import com.vanced.manager.repository.MainRepository
import com.vanced.manager.repository.MirrorRepository
import com.vanced.manager.ui.theme.ManagerTheme
import com.vanced.manager.repository.*
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import retrofit2.HttpException
class MainViewModel(
private val mainRepository: MainRepository,
private val mirrorRepository: MirrorRepository,
private val preferences: SharedPreferences,
private val appRepository: AppRepository,
private val preferenceRepository: PreferenceRepository,
private val app: Application,
) : AndroidViewModel(app) {
private val isRoot
get() = managerVariantPref == "root"
var appMode by mutableStateOf(preferenceRepository.managerMode)
var appTheme by mutableStateOf(preferenceRepository.managerTheme)
private val appCount: Int
get() = if (isRoot) 2 else 3
private val appCount
get() = when (appMode) {
ManagerMode.ROOT -> 2
ManagerMode.NONROOT -> 3
}
sealed class AppState {
data class Fetching(val placeholderAppsCount: Int) : AppState()
data class Success(val apps: List<App>) : AppState()
data class Error(val error: String) : AppState()
val isFetching get() = this is Fetching
val isSuccess get() = this is Success
val isError get() = this is Error
}
var appState by mutableStateOf<AppState>(AppState.Fetching(appCount))
var appState by mutableStateOf<ManagerState>(ManagerState.Fetching(appCount))
private set
var appTheme by mutableStateOf(
ManagerTheme.fromKey(
preferences.getString(
SettingsViewModel.MANAGER_THEME_KEY,
SettingsViewModel.THEME_SYSTEM_DEFAULT_VALUE
)
)
)
fun fetch() {
viewModelScope.launch {
appState = AppState.Fetching(appCount)
try {
supervisorScope {
appState = ManagerState.Fetching(appCount)
fetchData()
when (appMode) {
ManagerMode.ROOT -> {
appState = ManagerState.Success(
apps = listOf(
async { appRepository.getVancedYoutubeRoot() },
async { appRepository.getVancedYoutubeMusicRoot() }
).awaitAll()
)
}
ManagerMode.NONROOT -> {
appState = ManagerState.Success(
apps = listOf(
async { appRepository.getVancedYoutubeNonroot() },
async { appRepository.getVancedYoutubeMusicNonroot() },
async { appRepository.getVancedMicrog() }
).awaitAll()
)
}
}
}
} catch (e: HttpException) {
appState = ManagerState.Error(e.message())
} catch (e: Exception) {
appState = ManagerState.Error(e.toString())
}
}
}
fun launchApp(
appName: String,
appPackage: String,
packageName: String,
launchActivity: String
) {
val component = ComponentName(
/* pkg = */ appPackage,
/* cls = */ when (appName) {
VANCED_NAME -> "com.google.android.youtube.HomeActivity"
MUSIC_NAME -> "com.google.android.apps.youtube.music.activities.MusicActivity"
MICROG_NAME -> "org.microg.gms.ui.SettingsActivity"
else -> throw IllegalArgumentException("$appName is not a valid app")
}
)
try {
app.startActivity(
Intent().apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
setComponent(component)
}
)
} catch (e: ActivityNotFoundException) {
Log.d(TAG, "Unable to launch $appName")
e.printStackTrace()
val component = ComponentName(packageName, launchActivity)
val intent = Intent().apply {
setComponent(component)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
app.startActivity(intent)
}
//TODO implement root uninstallation
@ -99,28 +88,14 @@ class MainViewModel(
PM.uninstallPackage(appPackage, app)
}
private suspend fun fetchData(
fromMirror: Boolean = false
) {
try {
val repository = if (fromMirror) mirrorRepository else mainRepository
with(repository.fetch()) {
appState = AppState.Success(apps)
}
} catch (e: Exception) {
if (!fromMirror) {
fetchData(true)
return
}
}
val error = "failed to fetch: \n${e.stackTraceToString()}"
appState = AppState.Error(error)
Log.d(TAG, error)
}
}
companion object {
const val TAG = "MainViewModel"
}
sealed class ManagerState {
data class Fetching(val placeholderAppsCount: Int) : ManagerState()
data class Success(val apps: List<App>) : ManagerState()
data class Error(val error: String) : ManagerState()
val isFetching get() = this is Fetching
val isSuccess get() = this is Success
val isError get() = this is Error
}

View File

@ -1,29 +1,46 @@
package com.vanced.manager.ui.viewmodel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import com.vanced.manager.R
import com.vanced.manager.core.preferences.managerBooleanPreference
import com.vanced.manager.core.preferences.managerStringPreference
import com.vanced.manager.repository.ManagerMode
import com.vanced.manager.repository.ManagerTheme
import com.vanced.manager.repository.PreferenceRepository
class SettingsViewModel : ViewModel() {
class SettingsViewModel(
private val preferenceRepository: PreferenceRepository
) : ViewModel() {
companion object {
const val MANAGER_THEME_KEY = "manager_theme"
const val MANAGER_MODE_KEY = "manager_mode"
const val THEME_DARK_VALUE = "dark"
const val THEME_LIGHT_VALUE = "light"
const val THEME_SYSTEM_DEFAULT_VALUE = "system_default"
var managerUseCustomTabs by mutableStateOf(preferenceRepository.managerUseCustomTabs)
private set
var managerMode by mutableStateOf(preferenceRepository.managerMode)
private set
var managerTheme by mutableStateOf(preferenceRepository.managerTheme)
private set
fun saveManagerUseCustomTabs(value: Boolean) {
managerUseCustomTabs = value
preferenceRepository.managerUseCustomTabs = value
}
fun saveManagerMode(value: ManagerMode) {
managerMode = value
preferenceRepository.managerMode = value
}
fun saveManagerTheme(value: ManagerTheme) {
managerTheme = value
preferenceRepository.managerTheme = value
}
var managerUseCustomTabs by managerBooleanPreference(key = "manager_use_custom_tabs")
var managerMode by managerStringPreference(key = MANAGER_MODE_KEY, defaultValue = "nonroot")
var managerTheme by managerStringPreference(MANAGER_THEME_KEY, THEME_SYSTEM_DEFAULT_VALUE)
fun getThemeStringIdByValue(value: String): Int {
fun getThemeStringId(value: ManagerTheme): Int {
return when (value) {
THEME_DARK_VALUE -> R.string.settings_preference_theme_dark
THEME_LIGHT_VALUE -> R.string.settings_preference_theme_light
ManagerTheme.DARK -> R.string.settings_preference_theme_dark
ManagerTheme.LIGHT -> R.string.settings_preference_theme_light
else -> R.string.settings_option_system_default
}
}

View File

@ -5,9 +5,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.rounded.DeleteForever
import androidx.compose.material.icons.rounded.Download
import androidx.compose.material.icons.rounded.Launch
import androidx.compose.material.icons.rounded.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -15,13 +13,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import coil.compose.ImagePainter
import com.google.accompanist.placeholder.PlaceholderHighlight
import com.google.accompanist.placeholder.placeholder
import com.google.accompanist.placeholder.shimmer
import com.vanced.manager.R
import com.vanced.manager.domain.model.AppState
import com.vanced.manager.ui.component.ManagerElevatedCard
import com.vanced.manager.ui.component.ManagerListItem
import com.vanced.manager.ui.component.ManagerText
@ -34,9 +33,10 @@ import com.vanced.manager.ui.util.DefaultContentPaddingVertical
@Composable
fun AppCard(
appName: String,
appIcon: ImagePainter,
appIcon: Painter,
appInstalledVersion: String?,
appRemoteVersion: String?,
appState: AppState,
onAppDownloadClick: () -> Unit,
onAppUninstallClick: () -> Unit,
onAppLaunchClick: () -> Unit,
@ -101,10 +101,27 @@ fun AppCard(
}
}
IconButton(onClick = onAppDownloadClick) {
Icon(
imageVector = Icons.Rounded.Download,
contentDescription = "Install",
)
when (appState) {
AppState.NOT_INSTALLED -> {
Icon(
imageVector = Icons.Rounded.GetApp,
contentDescription = "Install",
)
}
AppState.INSTALLED -> {
Icon(
imageVector = Icons.Rounded.GetApp,
contentDescription = "Install",
)
}
AppState.NEEDS_UPDATE -> {
Icon(
imageVector = Icons.Rounded.Update,
contentDescription = "Update",
)
}
}
}
}
)

View File

@ -1,6 +1,5 @@
package com.vanced.manager.ui.widget
import android.content.Intent
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.foundation.layout.*
@ -13,11 +12,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.vanced.manager.core.preferences.holder.useCustomTabsPref
import com.vanced.manager.ui.component.ManagerElevatedCard
import com.vanced.manager.ui.component.ManagerText
import com.vanced.manager.ui.util.DefaultContentPaddingHorizontal
import com.vanced.manager.ui.util.DefaultContentPaddingVertical
import org.koin.androidx.compose.inject
//TODO this composable should not handle opening links
@Composable
@ -28,19 +27,14 @@ fun LinkCard(
modifier: Modifier = Modifier
) {
val context = LocalContext.current
val customTabs = remember { CustomTabsIntent.Builder().build() }
val customTabs: CustomTabsIntent by inject()
val uri = remember { Uri.parse(url) }
val intent = remember { Intent(Intent.ACTION_VIEW, uri) }
ManagerElevatedCard(
modifier = modifier
.height(100.dp)
.widthIn(min = 100.dp),
onClick = {
if (useCustomTabsPref) {
customTabs.launchUrl(context, uri)
} else {
context.startActivity(intent)
}
customTabs.launchUrl(context, uri)
}
) {
Box(

View File

@ -7,7 +7,8 @@ buildscript {
val kotlinVersion = "1.6.10"
dependencies {
classpath("com.android.tools.build:gradle:7.1.2")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
classpath(kotlin("gradle-plugin", version = kotlinVersion))
classpath(kotlin("serialization", version = kotlinVersion))
}
}