feature/home-screen (Future functionality) Added access to the local PackageManager

This commit is contained in:
HaliksaR 2020-11-28 07:11:03 +07:00
parent 6d98c79e35
commit 8b8e260945
42 changed files with 661 additions and 382 deletions

View File

@ -45,6 +45,10 @@ android.testOptions {
dependencies {
implementation project(':core-ui')
implementation project(':core-presentation')
implementation project(':library-network')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.core:core-ktx:1.3.2'
@ -52,7 +56,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-livedata-core-ktx:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
implementation 'com.google.android.material:material:1.3.0-alpha03'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
@ -71,7 +75,10 @@ dependencies {
testImplementation 'io.kotest:kotest-runner-junit5:4.3.1'
testImplementation 'io.kotest:kotest-assertions-core:4.3.1'
testImplementation 'io.kotest:kotest-property:4.3.1'
testImplementation "io.mockk:mockk:1.10.2"
androidTestImplementation "io.mockk:mockk-android:1.10.2"
androidTestImplementation 'androidx.test:core:1.3.0'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test:rules:1.3.0'
}

View File

@ -1,23 +0,0 @@
package com.vanced.manager.feature.home
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.vanced.manager.feature.home.test", appContext.packageName)
}
}

View File

@ -1,22 +0,0 @@
package com.vanced.manager.feature.home.data.datasource
import com.vanced.manager.feature.home.data.api.GetAppInformationApi
import com.vanced.manager.feature.home.data.dto.toEntity
import com.vanced.manager.feature.home.domain.entity.VancedApps
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
interface AppInformationDataSource {
suspend fun getAppInformation(): VancedApps
}
class AppInformationDataSourceImpl(
private val api: GetAppInformationApi
) : AppInformationDataSource {
override suspend fun getAppInformation(): VancedApps =
withContext(Dispatchers.IO) {
api.getAppInformation().toEntity()
}
}

View File

@ -0,0 +1,46 @@
package com.vanced.manager.feature.home.data.datasource
import com.vanced.manager.feature.home.data.api.GetAppInformationApi
import com.vanced.manager.feature.home.data.dto.toEntity
import com.vanced.manager.feature.home.domain.entity.MicroGInfo
import com.vanced.manager.feature.home.domain.entity.VancedManagerInfo
import com.vanced.manager.feature.home.domain.entity.YouTubeMusicVancedInfo
import com.vanced.manager.feature.home.domain.entity.YouTubeVancedInfo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
interface AppInformationRemoteDataSource {
suspend fun getMicroGInformation(): MicroGInfo
suspend fun getVancedManagerInformation(): VancedManagerInfo
suspend fun getYouTubeMusicVancedInformation(): YouTubeMusicVancedInfo
suspend fun getYouTubeVancedInformation(): YouTubeVancedInfo
}
class AppInformationDataSourceImpl(
private val api: GetAppInformationApi
) : AppInformationRemoteDataSource {
override suspend fun getMicroGInformation(): MicroGInfo =
withContext(Dispatchers.IO) {
api.getAppInformation().microG.toEntity()
}
override suspend fun getVancedManagerInformation(): VancedManagerInfo =
withContext(Dispatchers.IO) {
api.getAppInformation().vancedManager.toEntity()
}
override suspend fun getYouTubeMusicVancedInformation(): YouTubeMusicVancedInfo =
withContext(Dispatchers.IO) {
api.getAppInformation().youTubeMusicVanced.toEntity()
}
override suspend fun getYouTubeVancedInformation(): YouTubeVancedInfo =
withContext(Dispatchers.IO) {
api.getAppInformation().youTubeVanced.toEntity()
}
}

View File

@ -0,0 +1,30 @@
package com.vanced.manager.feature.home.data.datasource
import android.content.pm.PackageManager
import com.vanced.manager.feature.home.data.pkg.PkgManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
interface PkgInformationDataSource {
@Throws(PackageManager.NameNotFoundException::class)
suspend fun getVersionCode(packageName: String): Int
@Throws(PackageManager.NameNotFoundException::class)
suspend fun getVersionName(packageName: String): String
}
class PkgInformationDataSourceImpl(
private val pkgManager: PkgManager
) : PkgInformationDataSource {
override suspend fun getVersionCode(packageName: String): Int =
withContext(Dispatchers.IO) {
pkgManager.getVersionCode(packageName)
}
override suspend fun getVersionName(packageName: String): String =
withContext(Dispatchers.IO) {
pkgManager.getVersionName(packageName)
}
}

View File

@ -2,18 +2,18 @@ package com.vanced.manager.feature.home.data.dto
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.vanced.manager.feature.home.domain.entity.MicroG
import com.vanced.manager.feature.home.domain.entity.MicroGInfo
@JsonClass(generateAdapter = true)
data class MicroGDto(
@Json(name = "version") val version: String,
@Json(name = "versionCode") val versionCode: Long,
@Json(name = "versionCode") val versionCode: Int,
@Json(name = "url") val baseUrl: String,
@Json(name = "changelog") val changeLog: String
)
fun MicroGDto.toEntity() =
MicroG(version, versionCode, baseUrl, changeLog)
MicroGInfo(version, versionCode, baseUrl, changeLog)
fun MicroG.toDto() =
fun MicroGInfo.toDto() =
MicroGDto(version, versionCode, baseUrl, changeLog)

View File

@ -2,7 +2,6 @@ package com.vanced.manager.feature.home.data.dto
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.vanced.manager.feature.home.domain.entity.VancedApps
@JsonClass(generateAdapter = true)
data class VancedAppsDto(
@ -10,21 +9,4 @@ data class VancedAppsDto(
@Json(name = "vanced") val youTubeVanced: YouTubeVancedDto,
@Json(name = "music") val youTubeMusicVanced: YouTubeMusicVancedDto,
@Json(name = "microg") val microG: MicroGDto
)
fun VancedAppsDto.toEntity() =
VancedApps(
vancedManager = vancedManager.toEntity(),
youTubeVanced = youTubeVanced.toEntity(),
youTubeMusicVanced = youTubeMusicVanced.toEntity(),
microG = microG.toEntity()
)
fun VancedApps.toDto() =
VancedAppsDto(
vancedManager = vancedManager.toDto(),
youTubeVanced = youTubeVanced.toDto(),
youTubeMusicVanced = youTubeMusicVanced.toDto(),
microG = microG.toDto()
)
)

View File

@ -2,18 +2,18 @@ package com.vanced.manager.feature.home.data.dto
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.vanced.manager.feature.home.domain.entity.VancedManager
import com.vanced.manager.feature.home.domain.entity.VancedManagerInfo
@JsonClass(generateAdapter = true)
data class VancedManagerDto(
@Json(name = "version") val version: String,
@Json(name = "versionCode") val versionCode: Long,
@Json(name = "versionCode") val versionCode: Int,
@Json(name = "url") val baseUrl: String,
@Json(name = "changelog") val changeLog: String
)
fun VancedManagerDto.toEntity() =
VancedManager(version, versionCode, baseUrl, changeLog)
VancedManagerInfo(version, versionCode, baseUrl, changeLog)
fun VancedManager.toDto() =
fun VancedManagerInfo.toDto() =
VancedManagerDto(version, versionCode, baseUrl, changeLog)

View File

@ -2,18 +2,18 @@ package com.vanced.manager.feature.home.data.dto
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.vanced.manager.feature.home.domain.entity.YouTubeMusicVanced
import com.vanced.manager.feature.home.domain.entity.YouTubeMusicVancedInfo
@JsonClass(generateAdapter = true)
data class YouTubeMusicVancedDto(
@Json(name = "version") val version: String,
@Json(name = "versionCode") val versionCode: Long,
@Json(name = "versionCode") val versionCode: Int,
@Json(name = "url") val baseUrl: String,
@Json(name = "changelog") val changeLog: String
)
fun YouTubeMusicVancedDto.toEntity() =
YouTubeMusicVanced(version, versionCode, baseUrl, changeLog)
YouTubeMusicVancedInfo(version, versionCode, baseUrl, changeLog)
fun YouTubeMusicVanced.toDto() =
fun YouTubeMusicVancedInfo.toDto() =
YouTubeMusicVancedDto(version, versionCode, baseUrl, changeLog)

View File

@ -2,21 +2,20 @@ package com.vanced.manager.feature.home.data.dto
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.vanced.manager.feature.home.domain.entity.YouTubeVanced
import com.vanced.manager.feature.home.domain.entity.YouTubeVancedInfo
@JsonClass(generateAdapter = true)
data class YouTubeVancedDto(
@Json(name = "version") val version: String,
@Json(name = "versionCode") val versionCode: Long,
@Json(name = "versionCode") val versionCode: Int,
@Json(name = "url") val baseUrl: String,
@Json(name = "changelog") val changeLog: String,
@Json(name = "themes") val themes: List<String>,
@Json(name = "langs") val langs: List<String>
)
fun YouTubeVancedDto.toEntity() =
YouTubeVanced(version, versionCode, baseUrl, changeLog, themes, langs)
YouTubeVancedInfo(version, versionCode, baseUrl, changeLog, themes, langs)
fun YouTubeVanced.toDto() =
fun YouTubeVancedInfo.toDto() =
YouTubeVancedDto(version, versionCode, baseUrl, changeLog, themes, langs)

View File

@ -0,0 +1,45 @@
package com.vanced.manager.feature.home.data.pkg
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
}
@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
}
}
}
@Throws(PackageManager.NameNotFoundException::class)
override suspend fun getVersionName(packageName: String): String =
packageManager.getPackageInfo(packageName, PACKAGE_FLAG_ALL_OFF)
.versionName
}

View File

@ -1,25 +0,0 @@
package com.vanced.manager.feature.home.data.repository
import com.vanced.manager.feature.home.data.datasource.AppInformationDataSource
import com.vanced.manager.feature.home.domain.entity.*
import com.vanced.manager.feature.home.domain.repository.AppInformationRepository
class AppInformationRepositoryImpl(
private val dataSource: AppInformationDataSource
) : AppInformationRepository {
override suspend fun getAppInformation(): VancedApps =
dataSource.getAppInformation()
override suspend fun getMicroGInformation(): MicroG =
dataSource.getAppInformation().microG
override suspend fun getVancedManagerInformation(): VancedManager =
dataSource.getAppInformation().vancedManager
override suspend fun getYouTubeMusicVancedInformation(): YouTubeMusicVanced =
dataSource.getAppInformation().youTubeMusicVanced
override suspend fun getYouTubeVancedInformation(): YouTubeVanced =
dataSource.getAppInformation().youTubeVanced
}

View File

@ -0,0 +1,96 @@
package com.vanced.manager.feature.home.data.repository
import android.content.pm.PackageManager
import com.vanced.manager.feature.home.data.datasource.AppInformationRemoteDataSource
import com.vanced.manager.feature.home.data.datasource.PkgInformationDataSource
import com.vanced.manager.feature.home.domain.entity.App
import com.vanced.manager.feature.home.domain.entity.AppState
import com.vanced.manager.feature.home.domain.repository.AppRepository
class AppRepositoryImpl(
private val remoteDtaSource: AppInformationRemoteDataSource,
private val localDataSource: PkgInformationDataSource
) : AppRepository {
override suspend fun getMicroGInformation(
packageName: (App.MicroG.Companion) -> String
): App.MicroG {
val pkg = packageName(App.MicroG.Companion)
val remoteData = remoteDtaSource.getMicroGInformation()
val localCode = tryLocalCode(pkg)
return App.MicroG(
remoteInfo = remoteData,
localVersionCode = localCode,
localVersionName = tryLocalName(pkg),
state = compareCode(remoteData.versionCode, localCode)
)
}
override suspend fun getVancedManagerInformation(
packageName: (App.VancedManager.Companion) -> String
): App.VancedManager {
val pkg = packageName(App.VancedManager.Companion)
val remoteData = remoteDtaSource.getVancedManagerInformation()
val localCode = tryLocalCode(pkg)
return App.VancedManager(
remoteInfo = remoteData,
localVersionCode = localCode,
localVersionName = tryLocalName(pkg),
state = compareCode(remoteData.versionCode, localCode)
)
}
override suspend fun getYouTubeMusicVancedInformation(
packageName: (App.YouTubeMusicVanced.Companion) -> String
): App.YouTubeMusicVanced {
val pkg = packageName(App.YouTubeMusicVanced.Companion)
val remoteData = remoteDtaSource.getYouTubeMusicVancedInformation()
val localCode = tryLocalCode(pkg)
return App.YouTubeMusicVanced(
remoteInfo = remoteData,
localVersionCode = localCode,
localVersionName = tryLocalName(pkg),
state = compareCode(remoteData.versionCode, localCode)
)
}
override suspend fun getYouTubeVancedInformation(
packageName: (App.YouTubeVanced.Companion) -> String
): App.YouTubeVanced {
val pkg = packageName(App.YouTubeVanced.Companion)
val remoteData = remoteDtaSource.getYouTubeVancedInformation()
val localCode = tryLocalCode(pkg)
return App.YouTubeVanced(
remoteInfo = remoteData,
localVersionCode = localCode,
localVersionName = tryLocalName(pkg),
state = compareCode(remoteData.versionCode, localCode)
)
}
private fun compareCode(remote: Int, local: Int?): AppState =
if (local != null) {
when {
remote > local -> AppState.UPDATE
remote == local -> AppState.REINSTALL
remote < local -> AppState.REINSTALL
else -> throw IllegalArgumentException("Unknown versions")
}
} else {
AppState.INSTALL
}
private suspend fun tryLocalCode(packageName: String): Int? =
try {
localDataSource.getVersionCode(packageName)
} catch (exception: PackageManager.NameNotFoundException) {
null
}
private suspend fun tryLocalName(packageName: String): String? =
try {
localDataSource.getVersionName(packageName)
} catch (exception: PackageManager.NameNotFoundException) {
null
}
}

View File

@ -1,30 +1,48 @@
package com.vanced.manager.feature.home.di
import com.vanced.manager.feature.home.data.api.GetAppInformationApi
import com.vanced.manager.feature.home.data.datasource.AppInformationDataSource
import com.vanced.manager.feature.home.data.datasource.AppInformationDataSourceImpl
import com.vanced.manager.feature.home.data.repository.AppInformationRepositoryImpl
import com.vanced.manager.feature.home.domain.repository.AppInformationRepository
import com.vanced.manager.feature.home.domain.usecase.*
import com.vanced.manager.feature.home.data.datasource.AppInformationRemoteDataSource
import com.vanced.manager.feature.home.data.datasource.PkgInformationDataSource
import com.vanced.manager.feature.home.data.datasource.PkgInformationDataSourceImpl
import com.vanced.manager.feature.home.data.pkg.PkgManager
import com.vanced.manager.feature.home.data.pkg.PkgManagerImpl
import com.vanced.manager.feature.home.data.repository.AppRepositoryImpl
import com.vanced.manager.feature.home.domain.repository.AppRepository
import com.vanced.manager.feature.home.domain.usecase.GetMicroGInformationUseCase
import com.vanced.manager.feature.home.domain.usecase.GetVancedManagerInformationUseCase
import com.vanced.manager.feature.home.domain.usecase.GetYouTubeMusicVancedInformationUseCase
import com.vanced.manager.feature.home.domain.usecase.GetYouTubeVancedInformationUseCase
import com.vanced.manager.feature.home.presentation.HomeViewModel
import com.vanced.manager.library.network.service.createRetrofitService
import org.koin.android.ext.koin.androidContext
import org.koin.android.viewmodel.dsl.viewModel
import org.koin.dsl.module
internal val retrofitModule = module {
single<GetAppInformationApi?> { null }
single<GetAppInformationApi> { createRetrofitService(get()) }
}
internal val pkgManagerModule = module {
single<PkgManager> { PkgManagerImpl(androidContext().packageManager) }
}
internal val dataSourceModule = module {
single<AppInformationDataSource> { AppInformationDataSourceImpl(api = get()) }
single<AppInformationRemoteDataSource> { AppInformationDataSourceImpl(api = get()) }
single<PkgInformationDataSource> { PkgInformationDataSourceImpl(pkgManager = get()) }
}
internal val repositoryModule = module {
single<AppInformationRepository> { AppInformationRepositoryImpl(dataSource = get()) }
single<AppRepository> {
AppRepositoryImpl(
remoteDtaSource = get(),
localDataSource = get()
)
}
}
internal val useCaseModule = module {
single { GetAppInformationUseCase(repository = get()) }
single { GetMicroGInformationUseCase(repository = get()) }
single { GetVancedManagerInformationUseCase(repository = get()) }
single { GetYouTubeMusicVancedInformationUseCase(repository = get()) }
@ -34,17 +52,17 @@ internal val useCaseModule = module {
internal val viewModelModule = module {
viewModel {
HomeViewModel(
getAppInformationUseCase = get(),
getMicroGInformationUseCase = get(),
getVancedManagerInformationUseCase = get(),
getYouTubeVancedInformationUseCase = get(),
getYouTubeMusicVancedInformationUseCase = get()
getYouTubeMusicVancedInformationUseCase = get(),
getYouTubeVancedInformationUseCase = get()
)
}
}
val FeatureHomeModules = listOf(
retrofitModule,
pkgManagerModule,
dataSourceModule,
repositoryModule,
useCaseModule,

View File

@ -0,0 +1,55 @@
package com.vanced.manager.feature.home.domain.entity
sealed class App<R>(
open val remoteInfo: R,
open val localVersionCode: Int?,
open val localVersionName: String?,
open val state: AppState
) {
data class MicroG(
override val remoteInfo: MicroGInfo,
override val localVersionCode: Int?,
override val localVersionName: String?,
override val state: AppState
) : App<MicroGInfo>(remoteInfo, localVersionCode, localVersionName, state) {
companion object {
const val PKG_NAME = "com.mgoogle.android.gms"
}
}
data class VancedManager(
override val remoteInfo: VancedManagerInfo,
override val localVersionCode: Int?,
override val localVersionName: String?,
override val state: AppState
) : App<VancedManagerInfo>(remoteInfo, localVersionCode, localVersionName, state) {
companion object {
const val PKG_NAME = "com.vanced.manager" // TODO replace
}
}
data class YouTubeMusicVanced(
override val remoteInfo: YouTubeMusicVancedInfo,
override val localVersionCode: Int?,
override val localVersionName: String?,
override val state: AppState
) : App<YouTubeMusicVancedInfo>(remoteInfo, localVersionCode, localVersionName, state) {
companion object {
const val PKG_NAME = "com.vanced.android.apps.youtube.music"
const val PKG_NAME_ROOT = "com.google.android.apps.youtube.music"
}
}
data class YouTubeVanced(
override val remoteInfo: YouTubeVancedInfo,
override val localVersionCode: Int?,
override val localVersionName: String?,
override val state: AppState
) : App<YouTubeVancedInfo>(remoteInfo, localVersionCode, localVersionName, state) {
companion object {
const val PKG_NAME = "com.vanced.android.youtube"
const val PKG_NAME_ROOT = "com.google.android.youtube"
}
}
}

View File

@ -0,0 +1,7 @@
package com.vanced.manager.feature.home.domain.entity
enum class AppState {
INSTALL,
UPDATE,
REINSTALL
}

View File

@ -1,8 +1,8 @@
package com.vanced.manager.feature.home.domain.entity
data class MicroG(
data class MicroGInfo(
val version: String,
val versionCode: Long,
val versionCode: Int,
val baseUrl: String,
val changeLog: String
)

View File

@ -1,8 +0,0 @@
package com.vanced.manager.feature.home.domain.entity
data class VancedApps(
val vancedManager: VancedManager,
val youTubeVanced: YouTubeVanced,
val youTubeMusicVanced: YouTubeMusicVanced,
val microG: MicroG
)

View File

@ -1,8 +1,8 @@
package com.vanced.manager.feature.home.domain.entity
data class VancedManager(
data class VancedManagerInfo(
val version: String,
val versionCode: Long,
val versionCode: Int,
val baseUrl: String,
val changeLog: String
)

View File

@ -1,8 +1,8 @@
package com.vanced.manager.feature.home.domain.entity
data class YouTubeMusicVanced(
data class YouTubeMusicVancedInfo(
val version: String,
val versionCode: Long,
val versionCode: Int,
val baseUrl: String,
val changeLog: String
)

View File

@ -1,8 +1,8 @@
package com.vanced.manager.feature.home.domain.entity
data class YouTubeVanced(
data class YouTubeVancedInfo(
val version: String,
val versionCode: Long,
val versionCode: Int,
val baseUrl: String,
val changeLog: String,
val themes: List<String>,

View File

@ -1,16 +0,0 @@
package com.vanced.manager.feature.home.domain.repository
import com.vanced.manager.feature.home.domain.entity.*
interface AppInformationRepository {
suspend fun getAppInformation(): VancedApps
suspend fun getMicroGInformation(): MicroG
suspend fun getVancedManagerInformation(): VancedManager
suspend fun getYouTubeMusicVancedInformation(): YouTubeMusicVanced
suspend fun getYouTubeVancedInformation(): YouTubeVanced
}

View File

@ -0,0 +1,22 @@
package com.vanced.manager.feature.home.domain.repository
import com.vanced.manager.feature.home.domain.entity.App
interface AppRepository {
suspend fun getMicroGInformation(
packageName: (App.MicroG.Companion) -> String
): App.MicroG
suspend fun getVancedManagerInformation(
packageName: (App.VancedManager.Companion) -> String
): App.VancedManager
suspend fun getYouTubeMusicVancedInformation(
packageName: (App.YouTubeMusicVanced.Companion) -> String
): App.YouTubeMusicVanced
suspend fun getYouTubeVancedInformation(
packageName: (App.YouTubeVanced.Companion) -> String
): App.YouTubeVanced
}

View File

@ -1,12 +0,0 @@
package com.vanced.manager.feature.home.domain.usecase
import com.vanced.manager.feature.home.domain.entity.VancedApps
import com.vanced.manager.feature.home.domain.repository.AppInformationRepository
class GetAppInformationUseCase(
private val repository: AppInformationRepository
) {
suspend operator fun invoke(): VancedApps =
repository.getAppInformation()
}

View File

@ -1,12 +1,13 @@
package com.vanced.manager.feature.home.domain.usecase
import com.vanced.manager.feature.home.domain.entity.MicroG
import com.vanced.manager.feature.home.domain.repository.AppInformationRepository
import com.vanced.manager.feature.home.domain.entity.App
import com.vanced.manager.feature.home.domain.repository.AppRepository
class GetMicroGInformationUseCase(
private val repository: AppInformationRepository
private val repository: AppRepository
) {
suspend operator fun invoke(): MicroG =
repository.getMicroGInformation()
suspend operator fun invoke(
packageName: (App.MicroG.Companion) -> String
): App.MicroG = repository.getMicroGInformation(packageName)
}

View File

@ -1,12 +1,13 @@
package com.vanced.manager.feature.home.domain.usecase
import com.vanced.manager.feature.home.domain.entity.VancedManager
import com.vanced.manager.feature.home.domain.repository.AppInformationRepository
import com.vanced.manager.feature.home.domain.entity.App
import com.vanced.manager.feature.home.domain.repository.AppRepository
class GetVancedManagerInformationUseCase(
private val repository: AppInformationRepository
private val repository: AppRepository
) {
suspend operator fun invoke(): VancedManager =
repository.getVancedManagerInformation()
suspend operator fun invoke(
packageName: (App.VancedManager.Companion) -> String
): App.VancedManager = repository.getVancedManagerInformation(packageName)
}

View File

@ -1,12 +1,13 @@
package com.vanced.manager.feature.home.domain.usecase
import com.vanced.manager.feature.home.domain.entity.YouTubeMusicVanced
import com.vanced.manager.feature.home.domain.repository.AppInformationRepository
import com.vanced.manager.feature.home.domain.entity.App
import com.vanced.manager.feature.home.domain.repository.AppRepository
class GetYouTubeMusicVancedInformationUseCase(
private val repository: AppInformationRepository
private val repository: AppRepository
) {
suspend operator fun invoke(): YouTubeMusicVanced =
repository.getYouTubeMusicVancedInformation()
suspend operator fun invoke(
packageName: (App.YouTubeMusicVanced.Companion) -> String
): App.YouTubeMusicVanced = repository.getYouTubeMusicVancedInformation(packageName)
}

View File

@ -1,12 +1,13 @@
package com.vanced.manager.feature.home.domain.usecase
import com.vanced.manager.feature.home.domain.entity.YouTubeVanced
import com.vanced.manager.feature.home.domain.repository.AppInformationRepository
import com.vanced.manager.feature.home.domain.entity.App
import com.vanced.manager.feature.home.domain.repository.AppRepository
class GetYouTubeVancedInformationUseCase(
private val repository: AppInformationRepository
private val repository: AppRepository
) {
suspend operator fun invoke(): YouTubeVanced =
repository.getYouTubeVancedInformation()
suspend operator fun invoke(
packageName: (App.YouTubeVanced.Companion) -> String
): App.YouTubeVanced = repository.getYouTubeVancedInformation(packageName)
}

View File

@ -1,12 +1,60 @@
package com.vanced.manager.feature.home.presentation
import androidx.lifecycle.ViewModel
import com.vanced.manager.feature.home.domain.usecase.*
import androidx.lifecycle.viewModelScope
import com.vanced.manager.feature.home.domain.entity.App
import com.vanced.manager.feature.home.domain.usecase.GetMicroGInformationUseCase
import com.vanced.manager.feature.home.domain.usecase.GetVancedManagerInformationUseCase
import com.vanced.manager.feature.home.domain.usecase.GetYouTubeMusicVancedInformationUseCase
import com.vanced.manager.feature.home.domain.usecase.GetYouTubeVancedInformationUseCase
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
class HomeViewModel(
private val getAppInformationUseCase: GetAppInformationUseCase,
private val getMicroGInformationUseCase: GetMicroGInformationUseCase,
private val getVancedManagerInformationUseCase: GetVancedManagerInformationUseCase,
private val getYouTubeVancedInformationUseCase: GetYouTubeVancedInformationUseCase,
private val getYouTubeMusicVancedInformationUseCase: GetYouTubeMusicVancedInformationUseCase
) : ViewModel()
private val getYouTubeMusicVancedInformationUseCase: GetYouTubeMusicVancedInformationUseCase,
private val getYouTubeVancedInformationUseCase: GetYouTubeVancedInformationUseCase
) : ViewModel() {
private val _microG = MutableSharedFlow<App.MicroG>()
val microG: SharedFlow<App.MicroG> =
_microG.asSharedFlow()
private val _vancedManager = MutableSharedFlow<App.VancedManager>()
val vancedManager: SharedFlow<App.VancedManager> =
_vancedManager.asSharedFlow()
private val _youTubeVanced = MutableSharedFlow<App.YouTubeVanced>()
val youTubeVanced: SharedFlow<App.YouTubeVanced> =
_youTubeVanced.asSharedFlow()
private val _youTubeVancedRoot = MutableSharedFlow<App.YouTubeVanced>()
val youTubeVancedRoot: SharedFlow<App.YouTubeVanced> =
_youTubeVancedRoot.asSharedFlow()
private val _youTubeMusicVanced = MutableSharedFlow<App.YouTubeMusicVanced>()
val youTubeMusicVanced: SharedFlow<App.YouTubeMusicVanced> =
_youTubeMusicVanced.asSharedFlow()
private val _youTubeMusicVancedRoot = MutableSharedFlow<App.YouTubeMusicVanced>()
val youTubeMusicVancedRoot: SharedFlow<App.YouTubeMusicVanced> =
_youTubeMusicVancedRoot.asSharedFlow()
fun fetchAppInformation() {
viewModelScope.launch {
try {
_microG.emit(getMicroGInformationUseCase { it.PKG_NAME })
_vancedManager.emit(getVancedManagerInformationUseCase { it.PKG_NAME })
_youTubeVanced.emit(getYouTubeVancedInformationUseCase { it.PKG_NAME })
_youTubeVancedRoot.emit(getYouTubeVancedInformationUseCase { it.PKG_NAME_ROOT })
_youTubeMusicVanced.emit(getYouTubeMusicVancedInformationUseCase { it.PKG_NAME })
_youTubeMusicVancedRoot.emit(getYouTubeMusicVancedInformationUseCase { it.PKG_NAME_ROOT })
} catch (exception: Exception) {
//TODO state
}
}
}
}

View File

@ -1,5 +1,21 @@
package com.vanced.manager.feature.home.ui
import androidx.fragment.app.Fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import com.vanced.manager.core.ui.base.BindingFragment
import com.vanced.manager.feature.home.databinding.FragmentHomeBinding
import com.vanced.manager.feature.home.presentation.HomeViewModel
import org.koin.android.viewmodel.ext.android.viewModel
class HomeFragment : Fragment()
class HomeFragment : BindingFragment<FragmentHomeBinding>() {
private val viewModel: HomeViewModel by viewModel()
override fun binding(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = FragmentHomeBinding.inflate(inflater, container, false)
}

View File

@ -0,0 +1,53 @@
package com.vanced.manager.feature.home.ui.bind
import com.vanced.manager.feature.home.databinding.FragmentHomeBinding
import com.vanced.manager.feature.home.presentation.HomeViewModel
import com.vanced.manager.feature.home.ui.HomeFragment
internal fun HomeFragment.bindData(
binding: FragmentHomeBinding,
viewModel: HomeViewModel
) {
/* requireActivity().title = getString(R.string.title_home)
setHasOptionsMenu(true)
with(binding) {
homeRefresh.setOnRefreshListener { viewModel.fetchData() }
tooltip = ViewTooltip
.on(recyclerAppList)
.position(ViewTooltip.Position.TOP)
.autoHide(false, 0)
.color(ResourcesCompat.getColor(requireActivity().resources, R.color.Twitter, null))
.withShadow(false)
.corner(25)
.onHide {
prefs.edit { putBoolean("show_changelog_tooltip", false) }
}
.text(requireActivity().getString(R.string.app_changelog_tooltip))
if (prefs.getBoolean("show_changelog_tooltip", true)) {
tooltip.show()
}
recyclerAppList.apply {
layoutManager = LinearLayoutManager(requireActivity())
adapter = AppListAdapter(requireActivity(), viewModel, viewLifecycleOwner, tooltip)
setHasFixedSize(true)
}
recyclerSponsors.apply {
val lm = FlexboxLayoutManager(requireActivity())
lm.justifyContent = JustifyContent.SPACE_EVENLY
layoutManager = lm
setHasFixedSize(true)
adapter = SponsorAdapter(requireActivity(), viewModel)
}
recyclerLinks.apply {
val lm = FlexboxLayoutManager(requireActivity())
lm.justifyContent = JustifyContent.SPACE_EVENLY
layoutManager = lm
setHasFixedSize(true)
adapter = LinkAdapter(requireActivity(), viewModel)
}
}*/
}

View File

@ -0,0 +1,3 @@
package com.vanced.manager.feature.home.ui.uistate
sealed class UiState

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/home_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_app_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:nestedScrollingEnabled="false"
tools:itemCount="3"
tools:listitem="@layout/view_app" />
<!--android:text="@string/useful_links"
style="@style/CardTextHeader"-->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="12dp" />
<!--
tools:listitem="@layout/view_sponsor"-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_sponsors"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:nestedScrollingEnabled="false"
tools:itemCount="2" />
<!--
tools:listitem="@layout/view_social_link"-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_links"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:nestedScrollingEnabled="false"
android:paddingBottom="8dp"
tools:itemCount="6" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="discord">https://discord.gg/TUVd7rd</string>
<string name="telegram">https://t.me/joinchat/AAAAAEHf-pi4jH1SDlAL4w</string>
<string name="twitter">https://twitter.com/YTVanced</string>
<string name="reddit">https://reddit.com/r/vanced</string>
<string name="vanced">https://vanced.activity</string>
<string name="brave">https://brave.com/van874</string>
</resources>

View File

@ -2,7 +2,10 @@ package com.vanced.manager.feature.home.data.datasource
import com.vanced.manager.feature.home.data.api.GetAppInformationApi
import com.vanced.manager.feature.home.data.dto.*
import com.vanced.manager.feature.home.domain.entity.*
import com.vanced.manager.feature.home.domain.entity.MicroGInfo
import com.vanced.manager.feature.home.domain.entity.VancedManagerInfo
import com.vanced.manager.feature.home.domain.entity.YouTubeMusicVancedInfo
import com.vanced.manager.feature.home.domain.entity.YouTubeVancedInfo
import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe
import io.mockk.coEvery
@ -14,13 +17,6 @@ internal class AppInformationDataSourceImplTest : ShouldSpec() {
private val dataSource = AppInformationDataSourceImpl(api)
private val expectation = VancedApps(
VancedManager("", 1, "", ""),
YouTubeVanced("", 1, "", "", listOf(), listOf()),
YouTubeMusicVanced("", 1, "", ""),
MicroG("", 1, "", "")
)
private val verifiable = VancedAppsDto(
VancedManagerDto("", 1, "", ""),
YouTubeVancedDto("", 1, "", "", listOf(), listOf()),
@ -30,9 +26,51 @@ internal class AppInformationDataSourceImplTest : ShouldSpec() {
init { // https://kotest.io/styles/
context("return information") {
should("all apps") {
should("VancedManagerInfo") {
coEvery { api.getAppInformation() } returns verifiable
dataSource.getAppInformation() shouldBe expectation
dataSource
.getVancedManagerInformation() shouldBe
VancedManagerInfo(
version = "",
versionCode = 1,
baseUrl = "",
changeLog = ""
)
}
should("YouTubeVancedInfo") {
coEvery { api.getAppInformation() } returns verifiable
dataSource
.getYouTubeVancedInformation() shouldBe
YouTubeVancedInfo(
version = "",
versionCode = 1,
baseUrl = "",
changeLog = "",
themes = listOf(),
langs = listOf()
)
}
should("YouTubeMusicVancedInfo") {
coEvery { api.getAppInformation() } returns verifiable
dataSource
.getYouTubeMusicVancedInformation() shouldBe
YouTubeMusicVancedInfo(
version = "",
versionCode = 1,
baseUrl = "",
changeLog = ""
)
}
should("MicroGInfo") {
coEvery { api.getAppInformation() } returns verifiable
dataSource
.getMicroGInformation() shouldBe
MicroGInfo(
version = "",
versionCode = 1,
baseUrl = "",
changeLog = ""
)
}
}
}

View File

@ -0,0 +1,29 @@
package com.vanced.manager.feature.home.data.datasource
import com.vanced.manager.feature.home.data.pkg.PkgManager
import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe
import io.mockk.coEvery
import io.mockk.mockk
internal class PkgInformationDataSourceImplTest : ShouldSpec() {
private val pkgManager: PkgManager = mockk()
private val dataSource = PkgInformationDataSourceImpl(pkgManager)
init { // https://kotest.io/styles/
should("return version code") {
val testPackageName = "VancedManager"
val expectation = 4545
coEvery { pkgManager.getVersionCode(testPackageName) } returns expectation
dataSource.getVersionCode(testPackageName) shouldBe expectation
}
should("return version name") {
val testPackageName = "VancedManager"
val expectation = "VancedManagerName"
coEvery { pkgManager.getVersionName(testPackageName) } returns expectation
dataSource.getVersionName(testPackageName) shouldBe expectation
}
}
}

View File

@ -1,51 +0,0 @@
package com.vanced.manager.feature.home.data.repository
import com.vanced.manager.feature.home.data.datasource.AppInformationDataSource
import com.vanced.manager.feature.home.domain.entity.*
import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe
import io.mockk.coEvery
import io.mockk.mockk
internal class AppInformationRepositoryImplTest : ShouldSpec() {
private val dataSource: AppInformationDataSource = mockk()
private val repository = AppInformationRepositoryImpl(dataSource)
private val expectation = VancedApps(
VancedManager("", 1, "", ""),
YouTubeVanced("", 1, "", "", listOf(), listOf()),
YouTubeMusicVanced("", 1, "", ""),
MicroG("", 1, "", "")
)
init { // https://kotest.io/styles/
context("return information") {
should("all apps") {
coEvery { dataSource.getAppInformation() } returns expectation
repository.getAppInformation() shouldBe expectation
}
should("VancedManager") {
coEvery { dataSource.getAppInformation() } returns expectation
repository.getVancedManagerInformation() shouldBe expectation.vancedManager
}
should("YouTubeVanced") {
coEvery { dataSource.getAppInformation() } returns expectation
repository.getYouTubeVancedInformation() shouldBe expectation.youTubeVanced
}
should("YouTubeMusicVanced") {
coEvery { dataSource.getAppInformation() } returns expectation
repository.getYouTubeMusicVancedInformation() shouldBe expectation.youTubeMusicVanced
}
should("MicroG") {
coEvery { dataSource.getAppInformation() } returns expectation
repository.getMicroGInformation() shouldBe expectation.microG
}
}
}
}

View File

@ -1,30 +0,0 @@
package com.vanced.manager.feature.home.domain.usecase
import com.vanced.manager.feature.home.domain.entity.*
import com.vanced.manager.feature.home.domain.repository.AppInformationRepository
import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe
import io.mockk.coEvery
import io.mockk.mockk
internal class GetAppInformationUseCaseTest : ShouldSpec() {
private val repository: AppInformationRepository = mockk()
private val useCase = GetAppInformationUseCase(repository)
init { // https://habr.com/ru/post/520380/
should("return information all aps") {
val expectation = VancedApps(
VancedManager("", 1, "", ""),
YouTubeVanced("", 1, "", "", listOf(), listOf()),
YouTubeMusicVanced("", 1, "", ""),
MicroG("", 1, "", "")
)
coEvery { repository.getAppInformation() } returns expectation
useCase() shouldBe expectation
}
}
}

View File

@ -1,25 +0,0 @@
package com.vanced.manager.feature.home.domain.usecase
import com.vanced.manager.feature.home.domain.entity.MicroG
import com.vanced.manager.feature.home.domain.repository.AppInformationRepository
import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe
import io.mockk.coEvery
import io.mockk.mockk
internal class GetMicroGInformationUseCaseTest : ShouldSpec() {
private val repository: AppInformationRepository = mockk()
private val useCase = GetMicroGInformationUseCase(repository)
init { // https://github.com/mapbox/mapbox-navigation-android/blob/c5b8f6185ac1f22262664f14167da2d7ef2522e2/libnavigation-core/src/test/java/com/mapbox/navigation/core/routerefresh/RouteRefreshControllerTest.kt
should("return information MicroG") {
val expectation = MicroG("", 1, "", "")
coEvery { repository.getMicroGInformation() } returns expectation
useCase() shouldBe expectation
}
}
}

View File

@ -1,25 +0,0 @@
package com.vanced.manager.feature.home.domain.usecase
import com.vanced.manager.feature.home.domain.entity.VancedManager
import com.vanced.manager.feature.home.domain.repository.AppInformationRepository
import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe
import io.mockk.coEvery
import io.mockk.mockk
internal class GetVancedManagerInformationUseCaseTest : ShouldSpec() {
private val repository: AppInformationRepository = mockk()
private val useCase = GetVancedManagerInformationUseCase(repository)
init { // https://dev.to/kotest/testing-kotlin-js-with-kotest-i2j
should("return information VancedManager") {
val expectation = VancedManager("", 1, "", "")
coEvery { repository.getVancedManagerInformation() } returns expectation
useCase() shouldBe expectation
}
}
}

View File

@ -1,25 +0,0 @@
package com.vanced.manager.feature.home.domain.usecase
import com.vanced.manager.feature.home.domain.entity.YouTubeMusicVanced
import com.vanced.manager.feature.home.domain.repository.AppInformationRepository
import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe
import io.mockk.coEvery
import io.mockk.mockk
internal class GetYouTubeMusicVancedInformationUseCaseTest : ShouldSpec() {
private val repository: AppInformationRepository = mockk()
private val useCase = GetYouTubeMusicVancedInformationUseCase(repository)
init {
should("return information YouTubeMusicVanced") {
val expectation = YouTubeMusicVanced("", 1, "", "")
coEvery { repository.getYouTubeMusicVancedInformation() } returns expectation
useCase() shouldBe expectation
}
}
}

View File

@ -1,25 +0,0 @@
package com.vanced.manager.feature.home.domain.usecase
import com.vanced.manager.feature.home.domain.entity.YouTubeVanced
import com.vanced.manager.feature.home.domain.repository.AppInformationRepository
import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe
import io.mockk.coEvery
import io.mockk.mockk
internal class GetYouTubeVancedInformationUseCaseTest : ShouldSpec() {
private val repository: AppInformationRepository = mockk()
private val useCase = GetYouTubeVancedInformationUseCase(repository)
init {
should("return information YouTubeVanced") {
val expectation = YouTubeVanced("", 1, "", "", listOf(), listOf())
coEvery { repository.getYouTubeVancedInformation() } returns expectation
useCase() shouldBe expectation
}
}
}