implement new remote data type

This commit is contained in:
X1nto 2021-11-29 19:43:42 +04:00
parent b6533e3b37
commit ff510d7ce0
29 changed files with 257 additions and 219 deletions

View File

@ -16,7 +16,7 @@
*;
}
-keep class com.vanced.manager.network.model.JsonDto {
-keep class com.vanced.manager.network.model.DataDto {
*;
}

View File

@ -8,6 +8,7 @@ import okhttp3.OkHttpClient
import org.koin.dsl.module
import retrofit2.Retrofit
//TODO Add mirror support
val apiModule = module {
fun provideVancedAPI(

View File

@ -3,7 +3,7 @@ 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.JsonDtoMapper
import com.vanced.manager.network.model.DataDtoMapper
import org.koin.dsl.module
val mapperModule = module {
@ -15,9 +15,7 @@ val mapperModule = module {
fun provideJsonMapper(
appDtoMapper: AppDtoMapper
): JsonDtoMapper = JsonDtoMapper(
appDtoMapper = appDtoMapper
)
) = DataDtoMapper(appDtoMapper)
single { provideAppMapper(get(), get()) }
single { provideJsonMapper(get()) }

View File

@ -5,9 +5,8 @@ import org.koin.dsl.module
val networkModule = module {
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient()
}
fun provideOkHttpClient() =
OkHttpClient()
single { provideOkHttpClient() }
}

View File

@ -11,19 +11,15 @@ val packageManagerModule = module {
fun providePackageManager(
context: Context
): PkgManager {
return PkgManagerImpl(
): PkgManager =
PkgManagerImpl(
packageManager = context.packageManager
)
}
fun providePackageInformationDataSource(
pkgManager: PkgManager
): PackageInformationDataSource {
return PackageInformationDataSourceImpl(
pkgManager = pkgManager
)
}
): PackageInformationDataSource =
PackageInformationDataSourceImpl(pkgManager)
single { providePackageManager(get()) }
single { providePackageInformationDataSource(get()) }

View File

@ -4,6 +4,7 @@ import android.content.Context
import org.koin.dsl.module
val preferenceModule = module {
fun provideDatastore(
context: Context
) = context.getSharedPreferences("manager_settings", Context.MODE_PRIVATE)

View File

@ -1,20 +1,25 @@
package com.vanced.manager.di
import com.vanced.manager.network.JsonService
import com.vanced.manager.network.model.JsonDtoMapper
import com.vanced.manager.repository.JsonRepository
import com.vanced.manager.repository.JsonRepositoryImpl
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 org.koin.dsl.module
val repositoryModule = module {
fun provideJsonRepository(
jsonService: JsonService,
jsonDtoMapper: JsonDtoMapper
): JsonRepository = JsonRepositoryImpl(
jsonService,
jsonDtoMapper
)
fun provideMainRepository(
dataService: DataService,
dataDtoMapper: DataDtoMapper
) = MainRepository(dataService, dataDtoMapper)
single { provideJsonRepository(get(), get()) }
fun provideMirrorRepository(
dataService: DataService,
dataDtoMapper: DataDtoMapper
) = MirrorRepository(dataService, dataDtoMapper)
single { provideMainRepository(get(named("main")), get()) }
single { provideMirrorRepository(get(named("mirror")), get()) }
}

View File

@ -1,22 +1,35 @@
package com.vanced.manager.di
import com.google.gson.GsonBuilder
import com.vanced.manager.network.JsonService
import com.vanced.manager.network.DataService
import com.vanced.manager.network.util.BASE_GITHUB
import com.vanced.manager.network.util.BASE_MIRROR
import okhttp3.OkHttpClient
import org.koin.core.qualifier.named
import org.koin.dsl.module
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
val serviceModule = module {
fun provideRetrofitService(okHttpClient: OkHttpClient): JsonService =
Retrofit.Builder()
.baseUrl(BASE_GITHUB)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.client(okHttpClient)
.build()
.create(JsonService::class.java)
fun provideMainService(
okHttpClient: OkHttpClient
) = Retrofit.Builder()
.baseUrl(BASE_GITHUB)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.client(okHttpClient)
.build()
.create(DataService::class.java)
single { provideRetrofitService(get()) }
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()) }
}

View File

@ -1,5 +1,15 @@
package com.vanced.manager.di
import android.app.Application
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.ui.viewmodel.InstallViewModel
import com.vanced.manager.ui.viewmodel.MainViewModel
import org.koin.android.ext.koin.androidApplication
@ -7,6 +17,23 @@ import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
val viewModelModule = module {
viewModel { MainViewModel(get(), androidApplication()) }
viewModel { InstallViewModel(get(), get(), get(), get(), get(), get()) }
fun provideMainViewModel(
mainRepository: MainRepository,
mirrorRepository: MirrorRepository,
app: Application,
) = MainViewModel(mainRepository, mirrorRepository, app)
fun provideInstallViewModel(
vancedDownloader: VancedDownloader,
musicDownloader: MusicDownloader,
microgDownloader: MicrogDownloader,
vancedInstaller: VancedInstaller,
musicInstaller: MusicInstaller,
microgInstaller: MicrogInstaller,
) = InstallViewModel(vancedDownloader, musicDownloader, microgDownloader, vancedInstaller, musicInstaller, microgInstaller)
viewModel { provideMainViewModel(get(), get(), androidApplication()) }
viewModel { provideInstallViewModel(get(), get(), get(), get(), get(), get()) }
}

View File

@ -6,13 +6,9 @@ data class App(
val remoteVersionCode: Int,
val installedVersion: String?,
val installedVersionCode: Int?,
val installedVersionRoot: String?,
val installedVersionCodeRoot: Int?,
val iconUrl: String?,
val appStatus: AppStatus,
val appStatusRoot: AppStatus,
val packageName: String,
val packageNameRoot: String?,
val changelog: String,
val url: String?,
val versions: List<String>?,

View File

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

View File

@ -1,9 +0,0 @@
package com.vanced.manager.domain.model
data class Json(
val isMicrogBroken: Boolean,
val manager: App,
val vanced: App,
val music: App,
val microg: App
)

View File

@ -25,23 +25,25 @@ class PkgManagerImpl(
@SuppressLint("WrongConstant")
@Suppress("DEPRECATION")
@Throws(PackageManager.NameNotFoundException::class)
override suspend fun getVersionCode(packageName: String): Int {
return 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
}
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
override suspend fun getVersionName(
packageName: String
): String = packageManager
.getPackageInfo(packageName, PACKAGE_FLAG_ALL_OFF)
.versionName
}

View File

@ -0,0 +1,11 @@
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

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

View File

@ -5,11 +5,10 @@ import com.google.gson.annotations.SerializedName
data class AppDto(
@SerializedName("name") val name: String,
@SerializedName("version") val version: String,
@SerializedName("versionCode") val versionCode: Int,
@SerializedName("version_code") val versionCode: Int,
@SerializedName("changelog") val changelog: String,
@SerializedName("icon_url") val iconUrl: String? = null,
@SerializedName("icon_url") val iconUrl: String,
@SerializedName("package_name") val packageName: String,
@SerializedName("package_name_root") val packageNameRoot: String? = null,
@SerializedName("url") val url: String? = null,
@SerializedName("versions") val versions: List<String>? = null,
@SerializedName("themes") val themes: List<String>? = null,

View File

@ -17,8 +17,8 @@ import com.vanced.manager.network.util.VANCED_NAME
import java.util.*
class AppDtoMapper(
private val packageInformationDataSource: PackageInformationDataSource,
context: Context
private val pkgInfoDataSource: PackageInformationDataSource,
private val context: Context
) : EntityMapper<AppDto, App> {
private val latestVersionRadioButton =
@ -27,46 +27,40 @@ class AppDtoMapper(
key = "latest"
)
override suspend fun mapToModel(entity: AppDto): App =
with(entity) {
val localVersionCode = packageInformationDataSource.getVersionCode(packageName)
val localVersionCodeRoot =
packageInformationDataSource.getVersionCode(packageNameRoot ?: "")
val localVersionName = packageInformationDataSource.getVersionName(packageName)
val localVersionNameRoot =
packageInformationDataSource.getVersionName(packageNameRoot ?: "")
App(
name = name,
remoteVersion = version,
remoteVersionCode = versionCode,
installedVersion = localVersionName,
installedVersionCode = localVersionCode,
installedVersionRoot = localVersionNameRoot,
installedVersionCodeRoot = localVersionCodeRoot,
appStatus = compareVersionCodes(versionCode, localVersionCode),
appStatusRoot = compareVersionCodes(versionCode, localVersionCodeRoot),
packageName = packageName,
packageNameRoot = packageNameRoot,
iconUrl = iconUrl,
changelog = changelog,
url = url,
versions = versions,
themes = themes,
languages = languages,
installationOptions = getInstallationOptions(name, themes, versions, languages)
)
}
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 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,

View File

@ -0,0 +1,13 @@
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

@ -0,0 +1,26 @@
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,11 +0,0 @@
package com.vanced.manager.network.model
import com.google.gson.annotations.SerializedName
data class JsonDto(
@SerializedName("is_microg_broken") var isMicrogBroken: Boolean,
@SerializedName("manager") var manager: AppDto,
@SerializedName("vanced") var vanced: AppDto,
@SerializedName("music") var music: AppDto,
@SerializedName("microg") var microg: AppDto
)

View File

@ -1,21 +0,0 @@
package com.vanced.manager.network.model
import com.vanced.manager.domain.model.Json
import com.vanced.manager.domain.util.EntityMapper
class JsonDtoMapper(
private val appDtoMapper: AppDtoMapper
) : EntityMapper<JsonDto, Json> {
override suspend fun mapToModel(entity: JsonDto): Json =
with(entity) {
Json(
isMicrogBroken = isMicrogBroken,
manager = appDtoMapper.mapToModel(manager),
vanced = appDtoMapper.mapToModel(vanced),
music = appDtoMapper.mapToModel(music),
microg = appDtoMapper.mapToModel(microg)
)
}
}

View File

@ -1,7 +1,8 @@
package com.vanced.manager.network.util
const val BASE = "https://api.vancedapp.com/api/v1/"
const val BASE_GITHUB = "https://x1nto.github.io/VancedFiles/"
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 VANCED_NAME = "YouTube Vanced"
const val MUSIC_NAME = "YouTube Vanced Music"

View File

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

View File

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

View File

@ -1,16 +0,0 @@
package com.vanced.manager.repository
import com.vanced.manager.domain.model.Json
import com.vanced.manager.network.JsonService
import com.vanced.manager.network.model.JsonDtoMapper
class JsonRepositoryImpl(
private val service: JsonService,
private val mapper: JsonDtoMapper
) : JsonRepository {
override suspend fun fetch(): Json {
return mapper.mapToModel(service.get())
}
}

View File

@ -0,0 +1,14 @@
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

@ -0,0 +1,14 @@
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

@ -30,7 +30,6 @@ import com.vanced.manager.ui.component.menu.ManagerDropdownMenuItem
import com.vanced.manager.ui.component.text.ManagerText
import com.vanced.manager.ui.component.topappbar.ManagerTopAppBar
import com.vanced.manager.ui.resources.managerString
import com.vanced.manager.ui.util.DefaultContentPaddingHorizontal
import com.vanced.manager.ui.util.Screen
import com.vanced.manager.ui.viewmodel.MainViewModel
import com.vanced.manager.ui.widget.app.AppCard
@ -145,7 +144,6 @@ fun HomeLayout(
viewModel.launchApp(
appName = app.name,
appPackage = app.packageName,
appPackageRoot = app.packageNameRoot
)
},
onAppInfoClick = {
@ -191,8 +189,7 @@ fun HomeLayout(
}
managerCategory(homeCategorySupportUs) {
ScrollableItemRow(
modifier = Modifier
.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(),
items = sponsors
) { sponsor ->
ManagerLinkCard(
@ -204,8 +201,7 @@ fun HomeLayout(
}
managerCategory(homeCategorySocialMedia) {
ScrollableItemRow(
modifier = Modifier
.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(),
items = socialMedia
) { socialMedia ->
ManagerLinkCard(

View File

@ -12,22 +12,25 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.vanced.manager.core.installer.util.uninstallPackage
import com.vanced.manager.core.preferences.holder.managerVariantPref
import com.vanced.manager.core.preferences.holder.musicEnabled
import com.vanced.manager.core.preferences.holder.vancedEnabled
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.JsonRepository
import com.vanced.manager.repository.MainRepository
import com.vanced.manager.repository.MirrorRepository
import kotlinx.coroutines.launch
class MainViewModel(
private val repository: JsonRepository,
private val mainRepository: MainRepository,
private val mirrorRepository: MirrorRepository,
private val app: Application
) : AndroidViewModel(app) {
private val isNonroot
get() = managerVariantPref == "nonroot"
private val isRoot
get() = managerVariantPref == "root"
private val appCount: Int
get() = if (isRoot) 2 else 3
sealed class AppState {
data class Fetching(val placeholderAppsCount: Int) : AppState()
@ -42,37 +45,19 @@ class MainViewModel(
viewModelScope.launch {
appState = AppState.Fetching(appCount)
try {
with(repository.fetch()) {
val apps = mutableListOf<App>()
apps.apply {
if (vancedEnabled) add(vanced)
if (musicEnabled) add(music)
if (isNonroot) add(microg)
}
appState = AppState.Success(apps)
}
} catch (e: Exception) {
val error = "failed to fetch: \n${e.stackTraceToString()}"
appState = AppState.Error(error)
Log.d(TAG, error)
}
fetchData()
}
}
fun launchApp(
appName: String,
appPackage: String,
appPackageRoot: String?
) {
val pkg = if (isNonroot) appPackage else appPackageRoot ?: appPackage
val component = ComponentName(
/* pkg = */ appPackage,
/* cls = */ when (appName) {
VANCED_NAME -> "$pkg.HomeActivity"
MUSIC_NAME -> "$pkg.activities.MusicActivity"
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")
}
@ -98,16 +83,25 @@ class MainViewModel(
uninstallPackage(appPackage, app)
}
private val appCount: Int
get() {
var appsCount = 0
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
}
if (vancedEnabled) appsCount++
if (musicEnabled) appsCount++
if (isNonroot) appsCount++
return appsCount
val error = "failed to fetch: \n${e.stackTraceToString()}"
appState = AppState.Error(error)
Log.d(TAG, error)
}
}
companion object {
const val TAG = "MainViewModel"