Update in-app update checking
This commit is contained in:
@@ -18,7 +18,7 @@ import org.koin.core.context.startKoin
|
|||||||
import org.koitharu.kotatsu.bookmarks.bookmarksModule
|
import org.koitharu.kotatsu.bookmarks.bookmarksModule
|
||||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
import org.koitharu.kotatsu.core.db.databaseModule
|
import org.koitharu.kotatsu.core.db.databaseModule
|
||||||
import org.koitharu.kotatsu.core.github.githubModule
|
import org.koitharu.kotatsu.core.github.appUpdateModule
|
||||||
import org.koitharu.kotatsu.core.network.networkModule
|
import org.koitharu.kotatsu.core.network.networkModule
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.ui.uiModule
|
import org.koitharu.kotatsu.core.ui.uiModule
|
||||||
@@ -61,7 +61,7 @@ class KotatsuApp : Application() {
|
|||||||
modules(
|
modules(
|
||||||
networkModule,
|
networkModule,
|
||||||
databaseModule,
|
databaseModule,
|
||||||
githubModule,
|
appUpdateModule,
|
||||||
uiModule,
|
uiModule,
|
||||||
mainModule,
|
mainModule,
|
||||||
searchModule,
|
searchModule,
|
||||||
@@ -136,7 +136,7 @@ class KotatsuApp : Application() {
|
|||||||
StrictMode.ThreadPolicy.Builder()
|
StrictMode.ThreadPolicy.Builder()
|
||||||
.detectAll()
|
.detectAll()
|
||||||
.penaltyLog()
|
.penaltyLog()
|
||||||
.build()
|
.build(),
|
||||||
)
|
)
|
||||||
StrictMode.setVmPolicy(
|
StrictMode.setVmPolicy(
|
||||||
StrictMode.VmPolicy.Builder()
|
StrictMode.VmPolicy.Builder()
|
||||||
@@ -145,7 +145,7 @@ class KotatsuApp : Application() {
|
|||||||
.setClassInstanceLimit(PagesCache::class.java, 1)
|
.setClassInstanceLimit(PagesCache::class.java, 1)
|
||||||
.setClassInstanceLimit(MangaLoaderContext::class.java, 1)
|
.setClassInstanceLimit(MangaLoaderContext::class.java, 1)
|
||||||
.penaltyLog()
|
.penaltyLog()
|
||||||
.build()
|
.build(),
|
||||||
)
|
)
|
||||||
FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder()
|
FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder()
|
||||||
.penaltyDeath()
|
.penaltyDeath()
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.koitharu.kotatsu.core.github
|
||||||
|
|
||||||
|
import org.koin.android.ext.koin.androidContext
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val appUpdateModule
|
||||||
|
get() = module {
|
||||||
|
single { AppUpdateRepository(androidContext(), get()) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package org.koitharu.kotatsu.core.github
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.security.cert.CertificateFactory
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
|
import org.koitharu.kotatsu.parsers.util.await
|
||||||
|
import org.koitharu.kotatsu.parsers.util.byte2HexFormatted
|
||||||
|
import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
|
||||||
|
import org.koitharu.kotatsu.parsers.util.parseJsonArray
|
||||||
|
import org.koitharu.kotatsu.utils.ext.asArrayList
|
||||||
|
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||||
|
|
||||||
|
private const val CERT_SHA1 = "2C:19:C7:E8:07:61:2B:8E:94:51:1B:FD:72:67:07:64:5D:C2:58:AE"
|
||||||
|
|
||||||
|
class AppUpdateRepository(
|
||||||
|
private val context: Context,
|
||||||
|
private val okHttp: OkHttpClient,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val availableUpdate = MutableStateFlow<AppVersion?>(null)
|
||||||
|
|
||||||
|
fun observeAvailableUpdate() = availableUpdate.asStateFlow()
|
||||||
|
|
||||||
|
suspend fun getAvailableVersions(): List<AppVersion> {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.get()
|
||||||
|
.url("https://api.github.com/repos/KotatsuApp/Kotatsu/releases?page=1&per_page=10")
|
||||||
|
val jsonArray = okHttp.newCall(request.build()).await().parseJsonArray()
|
||||||
|
return jsonArray.mapJSONNotNull { json ->
|
||||||
|
val asset = json.optJSONArray("assets")?.optJSONObject(0) ?: return@mapJSONNotNull null
|
||||||
|
AppVersion(
|
||||||
|
id = json.getLong("id"),
|
||||||
|
url = json.getString("html_url"),
|
||||||
|
name = json.getString("name").removePrefix("v"),
|
||||||
|
apkSize = asset.getLong("size"),
|
||||||
|
apkUrl = asset.getString("browser_download_url"),
|
||||||
|
description = json.getString("body"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun fetchUpdate(): AppVersion? {
|
||||||
|
if (!isUpdateSupported()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return runCatching {
|
||||||
|
val currentVersion = VersionId(BuildConfig.VERSION_NAME)
|
||||||
|
val available = getAvailableVersions().asArrayList()
|
||||||
|
available.sortBy { it.versionId }
|
||||||
|
if (currentVersion.isStable) {
|
||||||
|
available.retainAll { it.versionId.isStable }
|
||||||
|
}
|
||||||
|
available.maxByOrNull { it.versionId }
|
||||||
|
?.takeIf { it.versionId > currentVersion }
|
||||||
|
}.onFailure {
|
||||||
|
it.printStackTraceDebug()
|
||||||
|
}.onSuccess {
|
||||||
|
availableUpdate.value = it
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isUpdateSupported(): Boolean {
|
||||||
|
return BuildConfig.DEBUG || getCertificateSHA1Fingerprint() == CERT_SHA1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
@SuppressLint("PackageManagerGetSignatures")
|
||||||
|
private fun getCertificateSHA1Fingerprint(): String? = runCatching {
|
||||||
|
val packageInfo = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
|
||||||
|
val signatures = requireNotNull(packageInfo?.signatures)
|
||||||
|
val cert: ByteArray = signatures.first().toByteArray()
|
||||||
|
val input: InputStream = ByteArrayInputStream(cert)
|
||||||
|
val cf = CertificateFactory.getInstance("X509")
|
||||||
|
val c = cf.generateCertificate(input) as X509Certificate
|
||||||
|
val md: MessageDigest = MessageDigest.getInstance("SHA1")
|
||||||
|
val publicKey: ByteArray = md.digest(c.encoded)
|
||||||
|
return publicKey.byte2HexFormatted()
|
||||||
|
}.onFailure { error ->
|
||||||
|
error.printStackTraceDebug()
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.koitharu.kotatsu.core.github
|
package org.koitharu.kotatsu.core.github
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -10,5 +11,9 @@ data class AppVersion(
|
|||||||
val url: String,
|
val url: String,
|
||||||
val apkSize: Long,
|
val apkSize: Long,
|
||||||
val apkUrl: String,
|
val apkUrl: String,
|
||||||
val description: String
|
val description: String,
|
||||||
) : Parcelable
|
) : Parcelable {
|
||||||
|
|
||||||
|
@IgnoredOnParcel
|
||||||
|
val versionId = VersionId(name)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.github
|
|
||||||
|
|
||||||
import org.koin.dsl.module
|
|
||||||
|
|
||||||
val githubModule
|
|
||||||
get() = module {
|
|
||||||
factory { GithubRepository(get()) }
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.github
|
|
||||||
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import org.koitharu.kotatsu.parsers.util.await
|
|
||||||
import org.koitharu.kotatsu.parsers.util.parseJson
|
|
||||||
|
|
||||||
class GithubRepository(private val okHttp: OkHttpClient) {
|
|
||||||
|
|
||||||
suspend fun getLatestVersion(): AppVersion {
|
|
||||||
val request = Request.Builder()
|
|
||||||
.get()
|
|
||||||
.url("https://api.github.com/repos/KotatsuApp/Kotatsu/releases/latest")
|
|
||||||
val json = okHttp.newCall(request.build()).await().parseJson()
|
|
||||||
val asset = json.getJSONArray("assets").getJSONObject(0)
|
|
||||||
return AppVersion(
|
|
||||||
id = json.getLong("id"),
|
|
||||||
url = json.getString("html_url"),
|
|
||||||
name = json.getString("name").removePrefix("v"),
|
|
||||||
apkSize = asset.getLong("size"),
|
|
||||||
apkUrl = asset.getString("browser_download_url"),
|
|
||||||
description = json.getString("body")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -63,6 +63,9 @@ class VersionId(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val VersionId.isStable: Boolean
|
||||||
|
get() = variantType.isEmpty()
|
||||||
|
|
||||||
fun VersionId(versionName: String): VersionId {
|
fun VersionId(versionName: String): VersionId {
|
||||||
val parts = versionName.substringBeforeLast('-').split('.')
|
val parts = versionName.substringBeforeLast('-').split('.')
|
||||||
val variant = versionName.substringAfterLast('-', "")
|
val variant = versionName.substringAfterLast('-', "")
|
||||||
@@ -73,4 +76,4 @@ fun VersionId(versionName: String): VersionId {
|
|||||||
variantType = variant.filter(Char::isLetter),
|
variantType = variant.filter(Char::isLetter),
|
||||||
variantNumber = variant.filter(Char::isDigit).toIntOrNull() ?: 0,
|
variantNumber = variant.filter(Char::isDigit).toIntOrNull() ?: 0,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,6 @@ val mainModule
|
|||||||
factory { ShortcutsUpdater(androidContext(), get(), get(), get()) }
|
factory { ShortcutsUpdater(androidContext(), get(), get(), get()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel { MainViewModel(get(), get()) }
|
viewModel { MainViewModel(get(), get(), get(), get()) }
|
||||||
viewModel { ProtectViewModel(get(), get()) }
|
viewModel { ProtectViewModel(get(), get()) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.koitharu.kotatsu.main.ui
|
package org.koitharu.kotatsu.main.ui
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.SparseIntArray
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup.MarginLayoutParams
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
@@ -9,12 +10,14 @@ import androidx.annotation.IdRes
|
|||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.app.ActivityOptionsCompat
|
import androidx.core.app.ActivityOptionsCompat
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
|
import androidx.core.util.size
|
||||||
import androidx.core.view.*
|
import androidx.core.view.*
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentTransaction
|
import androidx.fragment.app.FragmentTransaction
|
||||||
import androidx.fragment.app.commit
|
import androidx.fragment.app.commit
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.transition.TransitionManager
|
import androidx.transition.TransitionManager
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.appbar.AppBarLayout.LayoutParams.*
|
import com.google.android.material.appbar.AppBarLayout.LayoutParams.*
|
||||||
import com.google.android.material.navigation.NavigationBarView
|
import com.google.android.material.navigation.NavigationBarView
|
||||||
@@ -40,7 +43,6 @@ import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
|
|||||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment
|
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment
|
||||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
|
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
|
||||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
|
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
|
||||||
import org.koitharu.kotatsu.settings.AppUpdateChecker
|
|
||||||
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
|
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
|
||||||
import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment
|
import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment
|
||||||
import org.koitharu.kotatsu.settings.tools.ToolsFragment
|
import org.koitharu.kotatsu.settings.tools.ToolsFragment
|
||||||
@@ -50,7 +52,6 @@ import org.koitharu.kotatsu.tracker.ui.FeedFragment
|
|||||||
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
import org.koitharu.kotatsu.tracker.work.TrackWorker
|
||||||
import org.koitharu.kotatsu.utils.VoiceInputContract
|
import org.koitharu.kotatsu.utils.VoiceInputContract
|
||||||
import org.koitharu.kotatsu.utils.ext.*
|
import org.koitharu.kotatsu.utils.ext.*
|
||||||
import com.google.android.material.R as materialR
|
|
||||||
|
|
||||||
private const val TAG_PRIMARY = "primary"
|
private const val TAG_PRIMARY = "primary"
|
||||||
private const val TAG_SEARCH = "search"
|
private const val TAG_SEARCH = "search"
|
||||||
@@ -60,7 +61,8 @@ class MainActivity :
|
|||||||
AppBarOwner,
|
AppBarOwner,
|
||||||
View.OnClickListener,
|
View.OnClickListener,
|
||||||
View.OnFocusChangeListener,
|
View.OnFocusChangeListener,
|
||||||
SearchSuggestionListener, NavigationBarView.OnItemSelectedListener {
|
SearchSuggestionListener,
|
||||||
|
NavigationBarView.OnItemSelectedListener {
|
||||||
|
|
||||||
private val viewModel by viewModel<MainViewModel>()
|
private val viewModel by viewModel<MainViewModel>()
|
||||||
private val searchSuggestionViewModel by viewModel<SearchSuggestionViewModel>()
|
private val searchSuggestionViewModel by viewModel<SearchSuggestionViewModel>()
|
||||||
@@ -108,6 +110,7 @@ class MainActivity :
|
|||||||
viewModel.onError.observe(this, this::onError)
|
viewModel.onError.observe(this, this::onError)
|
||||||
viewModel.isLoading.observe(this, this::onLoadingStateChanged)
|
viewModel.isLoading.observe(this, this::onLoadingStateChanged)
|
||||||
viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged)
|
viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged)
|
||||||
|
viewModel.counters.observe(this, ::onCountersChanged)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||||
@@ -269,6 +272,20 @@ class MainActivity :
|
|||||||
Snackbar.make(binding.container, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show()
|
Snackbar.make(binding.container, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onCountersChanged(counters: SparseIntArray) {
|
||||||
|
repeat(counters.size) { i ->
|
||||||
|
val id = counters.keyAt(i)
|
||||||
|
val counter = counters.valueAt(i)
|
||||||
|
if (counter == 0) {
|
||||||
|
navBar.getBadge(id)?.isVisible = false
|
||||||
|
} else {
|
||||||
|
val badge = navBar.getOrCreateBadge(id)
|
||||||
|
badge.number = counter
|
||||||
|
badge.isVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun onLoadingStateChanged(isLoading: Boolean) {
|
private fun onLoadingStateChanged(isLoading: Boolean) {
|
||||||
binding.fab?.isEnabled = !isLoading
|
binding.fab?.isEnabled = !isLoading
|
||||||
}
|
}
|
||||||
@@ -330,13 +347,9 @@ class MainActivity :
|
|||||||
|
|
||||||
private fun onFirstStart() {
|
private fun onFirstStart() {
|
||||||
lifecycleScope.launchWhenResumed {
|
lifecycleScope.launchWhenResumed {
|
||||||
val isUpdateSupported = withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Default) {
|
||||||
TrackWorker.setup(applicationContext)
|
TrackWorker.setup(applicationContext)
|
||||||
SuggestionsWorker.setup(applicationContext)
|
SuggestionsWorker.setup(applicationContext)
|
||||||
AppUpdateChecker.isUpdateSupported(this@MainActivity)
|
|
||||||
}
|
|
||||||
if (isUpdateSupported) {
|
|
||||||
AppUpdateChecker(this@MainActivity).checkIfNeeded()
|
|
||||||
}
|
}
|
||||||
val settings = get<AppSettings>()
|
val settings = get<AppSettings>()
|
||||||
when {
|
when {
|
||||||
@@ -378,4 +391,4 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,54 @@
|
|||||||
package org.koitharu.kotatsu.main.ui
|
package org.koitharu.kotatsu.main.ui
|
||||||
|
|
||||||
|
import android.util.SparseIntArray
|
||||||
|
import androidx.core.util.set
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||||
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSection
|
import org.koitharu.kotatsu.core.github.AppUpdateRepository
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.prefs.observeAsLiveData
|
|
||||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||||
|
|
||||||
class MainViewModel(
|
class MainViewModel(
|
||||||
private val historyRepository: HistoryRepository,
|
private val historyRepository: HistoryRepository,
|
||||||
private val settings: AppSettings,
|
private val settings: AppSettings,
|
||||||
|
private val appUpdateRepository: AppUpdateRepository,
|
||||||
|
private val trackingRepository: TrackingRepository,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val onOpenReader = SingleLiveEvent<Manga>()
|
val onOpenReader = SingleLiveEvent<Manga>()
|
||||||
var defaultSection: AppSection
|
|
||||||
get() = settings.defaultSection
|
|
||||||
set(value) {
|
|
||||||
settings.defaultSection = value
|
|
||||||
}
|
|
||||||
|
|
||||||
val isSuggestionsEnabled = settings.observeAsLiveData(
|
|
||||||
context = viewModelScope.coroutineContext + Dispatchers.Default,
|
|
||||||
key = AppSettings.KEY_SUGGESTIONS,
|
|
||||||
valueProducer = { isSuggestionsEnabled },
|
|
||||||
)
|
|
||||||
|
|
||||||
val isTrackerEnabled = settings.observeAsLiveData(
|
|
||||||
context = viewModelScope.coroutineContext + Dispatchers.Default,
|
|
||||||
key = AppSettings.KEY_TRACKER_ENABLED,
|
|
||||||
valueProducer = { isTrackerEnabled },
|
|
||||||
)
|
|
||||||
|
|
||||||
val isResumeEnabled = historyRepository
|
val isResumeEnabled = historyRepository
|
||||||
.observeHasItems()
|
.observeHasItems()
|
||||||
.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, false)
|
.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, false)
|
||||||
|
|
||||||
|
val counters = combine(
|
||||||
|
appUpdateRepository.observeAvailableUpdate(),
|
||||||
|
trackingRepository.observeUpdatedMangaCount(),
|
||||||
|
) { appUpdate, tracks ->
|
||||||
|
val a = SparseIntArray(2)
|
||||||
|
a[R.id.nav_tools] = if (appUpdate != null) 1 else 0
|
||||||
|
a[R.id.nav_feed] = tracks
|
||||||
|
a
|
||||||
|
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, SparseIntArray(0))
|
||||||
|
|
||||||
|
init {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
appUpdateRepository.fetchUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun openLastReader() {
|
fun openLastReader() {
|
||||||
launchLoadingJob {
|
launchLoadingJob {
|
||||||
val manga = historyRepository.getLastOrNull() ?: throw EmptyHistoryException()
|
val manga = historyRepository.getLastOrNull() ?: throw EmptyHistoryException()
|
||||||
onOpenReader.call(manga)
|
onOpenReader.call(manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,31 +7,32 @@ import android.content.pm.PackageManager
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.koin.android.ext.android.get
|
|
||||||
import org.koitharu.kotatsu.BuildConfig
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.core.github.AppVersion
|
|
||||||
import org.koitharu.kotatsu.core.github.GithubRepository
|
|
||||||
import org.koitharu.kotatsu.core.github.VersionId
|
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
||||||
import org.koitharu.kotatsu.parsers.util.byte2HexFormatted
|
|
||||||
import org.koitharu.kotatsu.utils.FileSize
|
|
||||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.cert.CertificateFactory
|
import java.security.cert.CertificateFactory
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import com.google.android.material.R as materialR
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.koin.android.ext.android.get
|
||||||
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.github.AppUpdateRepository
|
||||||
|
import org.koitharu.kotatsu.core.github.AppVersion
|
||||||
|
import org.koitharu.kotatsu.core.github.VersionId
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.parsers.util.byte2HexFormatted
|
||||||
|
import org.koitharu.kotatsu.utils.FileSize
|
||||||
|
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||||
|
|
||||||
|
@Deprecated("")
|
||||||
class AppUpdateChecker(private val activity: ComponentActivity) {
|
class AppUpdateChecker(private val activity: ComponentActivity) {
|
||||||
|
|
||||||
private val settings = activity.get<AppSettings>()
|
private val settings = activity.get<AppSettings>()
|
||||||
private val repo = activity.get<GithubRepository>()
|
private val repo = activity.get<AppUpdateRepository>()
|
||||||
|
|
||||||
suspend fun checkIfNeeded(): Boolean? = if (
|
suspend fun checkIfNeeded(): Boolean? = if (
|
||||||
settings.isUpdateCheckingEnabled &&
|
settings.isUpdateCheckingEnabled &&
|
||||||
@@ -43,7 +44,7 @@ class AppUpdateChecker(private val activity: ComponentActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun checkNow() = runCatching {
|
suspend fun checkNow() = runCatching {
|
||||||
val version = repo.getLatestVersion()
|
val version = repo.fetchUpdate() ?: return@runCatching false
|
||||||
val newVersionId = VersionId(version.name)
|
val newVersionId = VersionId(version.name)
|
||||||
val currentVersionId = VersionId(BuildConfig.VERSION_NAME)
|
val currentVersionId = VersionId(BuildConfig.VERSION_NAME)
|
||||||
val result = newVersionId > currentVersionId
|
val result = newVersionId > currentVersionId
|
||||||
@@ -107,4 +108,4 @@ class AppUpdateChecker(private val activity: ComponentActivity) {
|
|||||||
error.printStackTraceDebug()
|
error.printStackTraceDebug()
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,5 +35,5 @@ val settingsModule
|
|||||||
viewModel { OnboardViewModel(get()) }
|
viewModel { OnboardViewModel(get()) }
|
||||||
viewModel { SourcesSettingsViewModel(get()) }
|
viewModel { SourcesSettingsViewModel(get()) }
|
||||||
viewModel { NewSourcesViewModel(get()) }
|
viewModel { NewSourcesViewModel(get()) }
|
||||||
viewModel { ToolsViewModel(get(), get()) }
|
viewModel { ToolsViewModel(get(), get(), get()) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.settings.tools
|
package org.koitharu.kotatsu.settings.tools
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.transition.TransitionManager
|
import android.transition.TransitionManager
|
||||||
@@ -10,6 +11,7 @@ import android.widget.CompoundButton
|
|||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.core.widget.TextViewCompat
|
import androidx.core.widget.TextViewCompat
|
||||||
@@ -19,9 +21,9 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.base.ui.BaseFragment
|
import org.koitharu.kotatsu.base.ui.BaseFragment
|
||||||
import org.koitharu.kotatsu.base.ui.widgets.SegmentedBarView
|
import org.koitharu.kotatsu.base.ui.widgets.SegmentedBarView
|
||||||
|
import org.koitharu.kotatsu.core.github.AppVersion
|
||||||
import org.koitharu.kotatsu.databinding.FragmentToolsBinding
|
import org.koitharu.kotatsu.databinding.FragmentToolsBinding
|
||||||
import org.koitharu.kotatsu.download.ui.DownloadsActivity
|
import org.koitharu.kotatsu.download.ui.DownloadsActivity
|
||||||
import org.koitharu.kotatsu.settings.AppUpdateChecker
|
|
||||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||||
import org.koitharu.kotatsu.settings.tools.model.StorageUsage
|
import org.koitharu.kotatsu.settings.tools.model.StorageUsage
|
||||||
import org.koitharu.kotatsu.utils.FileSize
|
import org.koitharu.kotatsu.utils.FileSize
|
||||||
@@ -32,7 +34,6 @@ class ToolsFragment :
|
|||||||
CompoundButton.OnCheckedChangeListener,
|
CompoundButton.OnCheckedChangeListener,
|
||||||
View.OnClickListener {
|
View.OnClickListener {
|
||||||
|
|
||||||
private var updateChecker: AppUpdateChecker? = null
|
|
||||||
private val viewModel by viewModel<ToolsViewModel>()
|
private val viewModel by viewModel<ToolsViewModel>()
|
||||||
|
|
||||||
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): FragmentToolsBinding {
|
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): FragmentToolsBinding {
|
||||||
@@ -51,12 +52,19 @@ class ToolsFragment :
|
|||||||
binding.switchIncognito.isChecked = it
|
binding.switchIncognito.isChecked = it
|
||||||
}
|
}
|
||||||
viewModel.storageUsage.observe(viewLifecycleOwner, ::onStorageUsageChanged)
|
viewModel.storageUsage.observe(viewLifecycleOwner, ::onStorageUsageChanged)
|
||||||
|
viewModel.appUpdate.observe(viewLifecycleOwner, ::onAppUpdateAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
when (v.id) {
|
when (v.id) {
|
||||||
R.id.button_settings -> startActivity(SettingsActivity.newIntent(v.context))
|
R.id.button_settings -> startActivity(SettingsActivity.newIntent(v.context))
|
||||||
R.id.button_downloads -> startActivity(DownloadsActivity.newIntent(v.context))
|
R.id.button_downloads -> startActivity(DownloadsActivity.newIntent(v.context))
|
||||||
|
R.id.button_download -> {
|
||||||
|
val url = viewModel.appUpdate.value?.apkUrl ?: return
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.data = url.toUri()
|
||||||
|
startActivity(Intent.createChooser(intent, getString(R.string.open_in_browser)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +80,15 @@ class ToolsFragment :
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onAppUpdateAvailable(version: AppVersion?) {
|
||||||
|
if (version == null) {
|
||||||
|
binding.cardUpdate.root.isVisible = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.cardUpdate.textPrimary.text = getString(R.string.new_version_s, version.name)
|
||||||
|
binding.cardUpdate.root.isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
private fun onStorageUsageChanged(usage: StorageUsage) {
|
private fun onStorageUsageChanged(usage: StorageUsage) {
|
||||||
val storageSegment = SegmentedBarView.Segment(usage.savedManga.percent, segmentColor(1))
|
val storageSegment = SegmentedBarView.Segment(usage.savedManga.percent, segmentColor(1))
|
||||||
val pagesSegment = SegmentedBarView.Segment(usage.pagesCache.percent, segmentColor(2))
|
val pagesSegment = SegmentedBarView.Segment(usage.pagesCache.percent, segmentColor(2))
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package org.koitharu.kotatsu.settings.tools
|
package org.koitharu.kotatsu.settings.tools
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.liveData
|
import androidx.lifecycle.liveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||||
|
import org.koitharu.kotatsu.core.github.AppUpdateRepository
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.prefs.observeAsLiveData
|
import org.koitharu.kotatsu.core.prefs.observeAsLiveData
|
||||||
import org.koitharu.kotatsu.local.data.CacheDir
|
import org.koitharu.kotatsu.local.data.CacheDir
|
||||||
@@ -13,9 +15,13 @@ import org.koitharu.kotatsu.settings.tools.model.StorageUsage
|
|||||||
|
|
||||||
class ToolsViewModel(
|
class ToolsViewModel(
|
||||||
private val storageManager: LocalStorageManager,
|
private val storageManager: LocalStorageManager,
|
||||||
|
private val appUpdateRepository: AppUpdateRepository,
|
||||||
private val settings: AppSettings,
|
private val settings: AppSettings,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
val appUpdate = appUpdateRepository.observeAvailableUpdate()
|
||||||
|
.asLiveData(viewModelScope.coroutineContext)
|
||||||
|
|
||||||
val storageUsage: LiveData<StorageUsage> = liveData(
|
val storageUsage: LiveData<StorageUsage> = liveData(
|
||||||
context = viewModelScope.coroutineContext + Dispatchers.Default,
|
context = viewModelScope.coroutineContext + Dispatchers.Default,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ abstract class TracksDao {
|
|||||||
@Query("SELECT chapters_new FROM tracks WHERE manga_id = :mangaId")
|
@Query("SELECT chapters_new FROM tracks WHERE manga_id = :mangaId")
|
||||||
abstract suspend fun findNewChapters(mangaId: Long): Int?
|
abstract suspend fun findNewChapters(mangaId: Long): Int?
|
||||||
|
|
||||||
|
@Query("SELECT chapters_new FROM tracks")
|
||||||
|
abstract fun observeNewChapters(): Flow<List<Int>>
|
||||||
|
|
||||||
@Query("SELECT chapters_new FROM tracks WHERE manga_id = :mangaId")
|
@Query("SELECT chapters_new FROM tracks WHERE manga_id = :mangaId")
|
||||||
abstract fun observeNewChapters(mangaId: Long): Flow<Int?>
|
abstract fun observeNewChapters(mangaId: Long): Flow<Int?>
|
||||||
|
|
||||||
@@ -42,4 +45,4 @@ abstract class TracksDao {
|
|||||||
insert(entity)
|
insert(entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.tracker.domain
|
|||||||
|
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
|
import java.util.*
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
@@ -18,7 +19,6 @@ import org.koitharu.kotatsu.tracker.data.toTrackingLogItem
|
|||||||
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
|
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
|
||||||
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
|
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
|
||||||
import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem
|
import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
private const val NO_ID = 0L
|
private const val NO_ID = 0L
|
||||||
|
|
||||||
@@ -34,6 +34,10 @@ class TrackingRepository(
|
|||||||
return db.tracksDao.observeNewChapters(mangaId).map { it ?: 0 }
|
return db.tracksDao.observeNewChapters(mangaId).map { it ?: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun observeUpdatedMangaCount(): Flow<Int> {
|
||||||
|
return db.tracksDao.observeNewChapters().map { list -> list.count { it > 0 } }
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getTracks(mangaList: Collection<Manga>): List<MangaTracking> {
|
suspend fun getTracks(mangaList: Collection<Manga>): List<MangaTracking> {
|
||||||
val ids = mangaList.mapToSet { it.id }
|
val ids = mangaList.mapToSet { it.id }
|
||||||
val tracks = db.tracksDao.findAll(ids).groupBy { it.mangaId }
|
val tracks = db.tracksDao.findAll(ids).groupBy { it.mangaId }
|
||||||
@@ -47,7 +51,7 @@ class TrackingRepository(
|
|||||||
result += MangaTracking(
|
result += MangaTracking(
|
||||||
manga = item,
|
manga = item,
|
||||||
lastChapterId = track?.lastChapterId ?: NO_ID,
|
lastChapterId = track?.lastChapterId ?: NO_ID,
|
||||||
lastCheck = track?.lastCheck?.takeUnless { it == 0L }?.let(::Date)
|
lastCheck = track?.lastCheck?.takeUnless { it == 0L }?.let(::Date),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@@ -59,7 +63,7 @@ class TrackingRepository(
|
|||||||
return MangaTracking(
|
return MangaTracking(
|
||||||
manga = manga,
|
manga = manga,
|
||||||
lastChapterId = track?.lastChapterId ?: NO_ID,
|
lastChapterId = track?.lastChapterId ?: NO_ID,
|
||||||
lastCheck = track?.lastCheck?.takeUnless { it == 0L }?.let(::Date)
|
lastCheck = track?.lastCheck?.takeUnless { it == 0L }?.let(::Date),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,4 +170,4 @@ class TrackingRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun Collection<MangaEntity>.toMangaList() = map { it.toManga(emptySet()) }
|
private fun Collection<MangaEntity>.toMangaList() = map { it.toManga(emptySet()) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package org.koitharu.kotatsu.utils.ext
|
|||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.liveData
|
import androidx.lifecycle.liveData
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlin.coroutines.EmptyCoroutineContext
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import org.koitharu.kotatsu.utils.BufferedObserver
|
import org.koitharu.kotatsu.utils.BufferedObserver
|
||||||
import kotlin.coroutines.CoroutineContext
|
|
||||||
import kotlin.coroutines.EmptyCoroutineContext
|
|
||||||
|
|
||||||
fun <T> LiveData<T>.requireValue(): T = checkNotNull(value) {
|
fun <T> LiveData<T>.requireValue(): T = checkNotNull(value) {
|
||||||
"LiveData value is null"
|
"LiveData value is null"
|
||||||
@@ -22,12 +22,12 @@ fun <T> LiveData<T>.observeWithPrevious(owner: LifecycleOwner, observer: Buffere
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <T> StateFlow<T>.asLiveDataDistinct(
|
fun <T> StateFlow<T>.asLiveDataDistinct(
|
||||||
context: CoroutineContext = EmptyCoroutineContext
|
context: CoroutineContext = EmptyCoroutineContext,
|
||||||
): LiveData<T> = asLiveDataDistinct(context, value)
|
): LiveData<T> = asLiveDataDistinct(context, value)
|
||||||
|
|
||||||
fun <T> Flow<T>.asLiveDataDistinct(
|
fun <T> Flow<T>.asLiveDataDistinct(
|
||||||
context: CoroutineContext = EmptyCoroutineContext,
|
context: CoroutineContext = EmptyCoroutineContext,
|
||||||
defaultValue: T
|
defaultValue: T,
|
||||||
): LiveData<T> = liveData(context) {
|
): LiveData<T> = liveData(context) {
|
||||||
if (latestValue == null) {
|
if (latestValue == null) {
|
||||||
emit(defaultValue)
|
emit(defaultValue)
|
||||||
@@ -37,4 +37,4 @@ fun <T> Flow<T>.asLiveDataDistinct(
|
|||||||
emit(it)
|
emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
@@ -14,7 +15,9 @@
|
|||||||
layout="@layout/layout_app_update"
|
layout="@layout/layout_app_update"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="@dimen/margin_normal" />
|
android:layout_margin="@dimen/margin_normal"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/layout_sync"
|
android:id="@+id/layout_sync"
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import org.junit.Test
|
|||||||
import org.koitharu.kotatsu.BuildConfig
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
import org.koitharu.kotatsu.parsers.util.await
|
import org.koitharu.kotatsu.parsers.util.await
|
||||||
|
|
||||||
class GithubRepositoryTest {
|
class AppUpdateRepositoryTest {
|
||||||
|
|
||||||
private val okHttpClient = OkHttpClient()
|
private val okHttpClient = OkHttpClient()
|
||||||
private val repository = GithubRepository(okHttpClient)
|
private val repository = AppUpdateRepository(okHttpClient)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getLatestVersion() = runTest {
|
fun getLatestVersion() = runTest {
|
||||||
@@ -23,11 +23,11 @@ class GithubRepositoryTest {
|
|||||||
Request.Builder()
|
Request.Builder()
|
||||||
.url(version.apkUrl)
|
.url(version.apkUrl)
|
||||||
.head()
|
.head()
|
||||||
.build()
|
.build(),
|
||||||
).await()
|
).await()
|
||||||
|
|
||||||
Assert.assertTrue(versionId <= VersionId(BuildConfig.VERSION_NAME))
|
Assert.assertTrue(versionId <= VersionId(BuildConfig.VERSION_NAME))
|
||||||
Assert.assertTrue(apkHead.isSuccessful)
|
Assert.assertTrue(apkHead.isSuccessful)
|
||||||
Assert.assertEquals(version.apkSize, apkHead.headersContentLength())
|
Assert.assertEquals(version.apkSize, apkHead.headersContentLength())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user