Compare commits
29 Commits
f670e222c6
...
1d5ab3205e
Author | SHA1 | Date | |
---|---|---|---|
1d5ab3205e | |||
df4903cae5 | |||
2a78be69b5 | |||
8c69bb8c3c | |||
9203012a97 | |||
2a44162c5a | |||
20588aab81 | |||
0c8e49214f | |||
97d5063339 | |||
7c37b183d7 | |||
82c4a5a1f9 | |||
47b7062e16 | |||
b9497ca939 | |||
1258ed3ad3 | |||
d838f509d4 | |||
3c5b606a02 | |||
d1481a1db6 | |||
d654b1b0bd | |||
f56861a3c2 | |||
492e7e4aed | |||
551a3e3caa | |||
c224b8a0b3 | |||
13ea7a693b | |||
0f3c48dd8e | |||
d4c2373bac | |||
4f32097821 | |||
|
37fa4a1a8e | ||
|
112194dd4f | ||
72d9ef92d2 |
@ -181,22 +181,34 @@ dependencies {
|
||||
implementation("androidx.viewpager2:viewpager2:1.1.0-beta01")
|
||||
|
||||
//Dependency Injection
|
||||
implementation("org.kodein.di:kodein-di:7.12.0")
|
||||
implementation("org.kodein.di:kodein-di-framework-android-x:7.12.0")
|
||||
implementation("org.kodein.di:kodein-di:7.14.0")
|
||||
implementation("org.kodein.di:kodein-di-framework-android-x:7.14.0")
|
||||
implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.14.0")
|
||||
|
||||
//Settings
|
||||
implementation("com.russhwolf:multiplatform-settings-no-arg:0.9")
|
||||
|
||||
//Logging
|
||||
implementation("io.github.aakira:napier:2.6.1")
|
||||
|
||||
//PhotoView
|
||||
implementation("com.github.chrisbanes:PhotoView:2.3.0")
|
||||
|
||||
implementation("androidx.core:core-ktx:1.7.0")
|
||||
implementation("androidx.core:core-ktx:1.8.0")
|
||||
|
||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0")
|
||||
implementation("androidx.lifecycle:lifecycle-common-java8:2.4.0")
|
||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
|
||||
implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime:2.5.1")
|
||||
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
|
||||
|
||||
implementation("androidx.room:room-ktx:2.4.0-beta01")
|
||||
kapt("androidx.room:room-compiler:2.4.0-beta01")
|
||||
|
||||
implementation("android.arch.work:work-runtime-ktx:1.0.1")
|
||||
|
||||
// Network information
|
||||
// TODO: When updating this library, check if the todo in RepositoryImpl.startNetwork can be resolved
|
||||
// TODO: Include this library once this is merged https://github.com/ln-12/multiplatform-connectivity-status/pull/4
|
||||
// implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0")
|
||||
implementation(files("../shared/libs/multiplatform-connectivity-status.aar"))
|
||||
}
|
@ -44,9 +44,9 @@ import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toEntity
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toView
|
||||
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||
@ -126,7 +126,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
private var recyclerAdapter: RecyclerView.Adapter<*>? = null
|
||||
|
||||
private var fromTabShortcut: Boolean = false
|
||||
private var offlineShortcut: Boolean = false
|
||||
|
||||
private lateinit var tagsBadge: Map<Long, Int>
|
||||
|
||||
@ -153,7 +152,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
val view = binding.root
|
||||
|
||||
fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1
|
||||
offlineShortcut = intent.getBooleanExtra("startOffline", false)
|
||||
repository.offlineOverride = intent.getBooleanExtra("startOffline", false)
|
||||
|
||||
if (fromTabShortcut) {
|
||||
elementsShown = ItemType.fromInt(intent.getIntExtra("shortcutTab", ItemType.UNREAD.position))
|
||||
@ -175,7 +174,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
AppDatabase::class.java, "selfoss-database"
|
||||
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
|
||||
|
||||
|
||||
customTabActivityHelper = CustomTabActivityHelper()
|
||||
|
||||
dataBase = AndroidDeviceDatabase(applicationContext)
|
||||
@ -197,7 +195,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
R.color.refresh_progress_3
|
||||
)
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
offlineShortcut = false
|
||||
repository.offlineOverride = false
|
||||
lastFetchDone = false
|
||||
handleDrawerItems()
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
@ -689,7 +687,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
var sources: List<SelfossModel.Source>?
|
||||
|
||||
fun sourcesApiCall() {
|
||||
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut) && updateSources) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val response = repository.getSources()
|
||||
if (response != null) {
|
||||
@ -706,15 +703,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut) && updateSources) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
tags = repository.getTags()
|
||||
sourcesApiCall()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
PrimaryDrawerItem().apply {
|
||||
@ -944,13 +938,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
private fun reloadBadges() {
|
||||
if (displayUnreadCount || displayAllCount) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
if (applicationContext.isNetworkAvailable()) {
|
||||
repository.reloadBadges()
|
||||
reloadBadgeContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadBadgeContent() {
|
||||
if (displayUnreadCount) {
|
||||
@ -1021,13 +1013,13 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.refresh -> {
|
||||
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
||||
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
|
||||
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
|
||||
// TODO: Use Dispatchers.IO
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val updatedRemote = repository.updateRemote()
|
||||
if (updatedRemote) {
|
||||
// TODO: Send toast messages from the repository
|
||||
Toast.makeText(
|
||||
this@HomeActivity,
|
||||
R.string.refresh_success_response, Toast.LENGTH_LONG
|
||||
@ -1043,16 +1035,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
R.id.readAll -> {
|
||||
if (elementsShown == ItemType.UNREAD) {
|
||||
needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
|
||||
binding.swipeRefreshLayout.isRefreshing = true
|
||||
|
||||
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val success = repository.markAllAsRead(items)
|
||||
if (success) {
|
||||
@ -1078,7 +1066,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
R.id.action_disconnect -> {
|
||||
@ -1127,7 +1114,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
}
|
||||
|
||||
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val actions = db.actionsDao().actions()
|
||||
|
||||
@ -1142,5 +1128,4 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||
import com.russhwolf.settings.Settings
|
||||
@ -202,7 +201,6 @@ class LoginActivity() : AppCompatActivity(), DIAware {
|
||||
|
||||
repository.refreshLoginInformation(url, login, password, httpLogin, httpPassword, isWithSelfSignedCert)
|
||||
|
||||
if (this@LoginActivity.isNetworkAvailable(this@LoginActivity.findViewById(R.id.loginForm))) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val result = repository.login()
|
||||
if (result) {
|
||||
@ -213,7 +211,6 @@ class LoginActivity() : AppCompatActivity(), DIAware {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
showProgress(false)
|
||||
}
|
||||
}
|
||||
|
@ -7,32 +7,48 @@ import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import androidx.preference.PreferenceManager
|
||||
import bou.amine.apps.readerforselfossv2.DI.networkModule
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
|
||||
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.ftinc.scoop.Scoop
|
||||
import com.github.`ln-12`.library.ConnectivityStatus
|
||||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||
import com.russhwolf.settings.Settings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import io.github.aakira.napier.DebugAntilog
|
||||
import io.github.aakira.napier.Napier
|
||||
import org.kodein.di.*
|
||||
|
||||
class MyApp : MultiDexApplication(), DIAware {
|
||||
|
||||
override val di by DI.lazy {
|
||||
import(networkModule)
|
||||
bind<Repository>() with singleton { Repository(instance(), instance()) }
|
||||
bind<Repository>() with singleton { Repository(instance(), instance(), ConnectivityStatus(applicationContext)) }
|
||||
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
|
||||
}
|
||||
|
||||
private val repository: Repository by instance()
|
||||
private val viewModel: AppViewModel by instance()
|
||||
private lateinit var config: Config
|
||||
private lateinit var settings : Settings
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Napier.base(DebugAntilog())
|
||||
config = Config()
|
||||
settings = Settings()
|
||||
|
||||
@ -43,6 +59,18 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
tryToHandleBug()
|
||||
|
||||
handleNotificationChannels()
|
||||
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(repository))
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
viewModel.toastMessageProvider.collect { toastMessage ->
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
toastMessage,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNotificationChannels() {
|
||||
@ -102,4 +130,17 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppLifeCycleObserver(val repository: Repository) : DefaultLifecycleObserver {
|
||||
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
super.onResume(owner)
|
||||
repository.startNetwork()
|
||||
}
|
||||
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
repository.stopNetwork()
|
||||
super.onPause(owner)
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,6 @@ import bou.amine.apps.readerforselfossv2.android.adapters.SourcesListAdapter
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySourcesBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import com.ftinc.scoop.Scoop
|
||||
@ -64,7 +63,6 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.layoutManager = mLayoutManager
|
||||
|
||||
if (this@SourcesActivity.isNetworkAvailable(binding.recyclerView)) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val response = repository.getSources()
|
||||
if (response != null) {
|
||||
@ -89,7 +87,6 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.fab.setOnClickListener {
|
||||
startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
|
||||
|
@ -16,7 +16,6 @@ import bou.amine.apps.readerforselfossv2.android.utils.*
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
@ -110,7 +109,6 @@ class ItemCardAdapter(
|
||||
|
||||
binding.favButton.setOnClickListener {
|
||||
val item = items[bindingAdapterPosition]
|
||||
if (c.isNetworkAvailable()) {
|
||||
if (item.starred) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.unstarr(item)
|
||||
@ -127,7 +125,6 @@ class ItemCardAdapter(
|
||||
binding.favButton.isSelected = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.shareBtn.setOnClickListener {
|
||||
val item = items[bindingAdapterPosition]
|
||||
|
@ -14,7 +14,6 @@ import bou.amine.apps.readerforselfossv2.android.model.getIcon
|
||||
import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.toTextDrawableString
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
@ -78,7 +77,6 @@ class SourcesListAdapter(
|
||||
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
|
||||
|
||||
deleteBtn.setOnClickListener {
|
||||
if (c.isNetworkAvailable(null)) {
|
||||
val (id) = items[adapterPosition]
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val successfullyDeletedSource = repository.deleteSource(id)
|
||||
@ -98,4 +96,3 @@ class SourcesListAdapter(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATIO
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||
@ -44,9 +44,7 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con
|
||||
override fun doWork(): Result {
|
||||
val settings = Settings()
|
||||
val periodicRefresh = settings.getBoolean("periodic_refresh", false)
|
||||
if (periodicRefresh) {
|
||||
|
||||
if (context.isNetworkAvailable()) {
|
||||
if (periodicRefresh && isNetworkAccessible(context)) {
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val notificationManager =
|
||||
@ -94,7 +92,6 @@ override fun doWork(): Result {
|
||||
}
|
||||
}
|
||||
|
||||
if (context.isNetworkAvailable()) {
|
||||
launch {
|
||||
try {
|
||||
val newItems = repository.allItems(ItemType.UNREAD)
|
||||
@ -106,8 +103,6 @@ override fun doWork(): Result {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,6 @@ import bou.amine.apps.readerforselfossv2.android.utils.*
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
||||
@ -276,7 +275,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
}
|
||||
|
||||
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) {
|
||||
if ((context != null && requireContext().isNetworkAvailable(null)) || context == null) {
|
||||
if (repository.isNetworkAvailable()) {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
val parser = MercuryApi()
|
||||
|
||||
@ -317,7 +316,10 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
Glide
|
||||
.with(requireContext())
|
||||
.asBitmap()
|
||||
.loadMaybeBasicAuth(config, response.body()!!.lead_image_url.orEmpty())
|
||||
.loadMaybeBasicAuth(
|
||||
config,
|
||||
response.body()!!.lead_image_url.orEmpty()
|
||||
)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(binding.imageView)
|
||||
} catch (e: Exception) {
|
||||
|
@ -1,52 +1,14 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.utils.network
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
var snackBarShown = false
|
||||
var view: View? = null
|
||||
lateinit var s: Snackbar
|
||||
|
||||
fun Context.isNetworkAvailable(
|
||||
v: View? = null,
|
||||
overrideOffline: Boolean = false
|
||||
): Boolean {
|
||||
val networkIsAccessible = isNetworkAccessible(this)
|
||||
|
||||
if (v != null && (!networkIsAccessible || overrideOffline) && (!snackBarShown || v != view)) {
|
||||
view = v
|
||||
s = Snackbar
|
||||
.make(
|
||||
v,
|
||||
R.string.no_network_connectivity,
|
||||
Snackbar.LENGTH_INDEFINITE
|
||||
)
|
||||
|
||||
s.setAction(android.R.string.ok) {
|
||||
snackBarShown = false
|
||||
s.dismiss()
|
||||
}
|
||||
|
||||
val view = s.view
|
||||
val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text)
|
||||
tv.setTextColor(Color.WHITE)
|
||||
s.show()
|
||||
snackBarShown = true
|
||||
}
|
||||
if (snackBarShown && networkIsAccessible && !overrideOffline) {
|
||||
s.dismiss()
|
||||
}
|
||||
return if(overrideOffline) overrideOffline else networkIsAccessible
|
||||
}
|
||||
|
||||
private fun isNetworkAccessible(context: Context): Boolean {
|
||||
fun isNetworkAccessible(context: Context): Boolean {
|
||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
|
@ -0,0 +1,29 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AppViewModel(private val repository: Repository) : ViewModel() {
|
||||
private val _toastMessageProvider = MutableSharedFlow<Int>()
|
||||
val toastMessageProvider = _toastMessageProvider.asSharedFlow()
|
||||
private var wasConnected = true
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
repository.isConnectionAvailable.collect { isConnected ->
|
||||
if (isConnected && !wasConnected && repository.connectionMonitored) {
|
||||
_toastMessageProvider.emit(R.string.network_connectivity_retrieved)
|
||||
wasConnected = true
|
||||
} else if (!isConnected && wasConnected && repository.connectionMonitored){
|
||||
_toastMessageProvider.emit(R.string.network_connectivity_lost)
|
||||
wasConnected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -143,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Not connected !</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sync articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||
|
@ -36,6 +36,11 @@ kotlin {
|
||||
|
||||
//Logging
|
||||
implementation("io.github.aakira:napier:2.6.1")
|
||||
|
||||
// Network information
|
||||
// TODO: Include this library once this is merged https://github.com/ln-12/multiplatform-connectivity-status/pull/4
|
||||
//implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0")
|
||||
implementation(files("libs/multiplatform-connectivity-status.aar"))
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
|
BIN
shared/libs/multiplatform-connectivity-status.aar
Normal file
BIN
shared/libs/multiplatform-connectivity-status.aar
Normal file
Binary file not shown.
@ -5,16 +5,19 @@ import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||
import com.github.`ln-12`.library.ConnectivityStatus
|
||||
import com.russhwolf.settings.Settings
|
||||
import io.github.aakira.napier.Napier
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService) {
|
||||
class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService, val connectivityStatus: ConnectivityStatus) {
|
||||
val settings = Settings()
|
||||
|
||||
var items = ArrayList<SelfossModel.Item>()
|
||||
val isConnectionAvailable = connectivityStatus.isNetworkConnected
|
||||
var connectionMonitored = false
|
||||
|
||||
var baseUrl = apiDetails.getBaseUrl()
|
||||
lateinit var dateUtils: DateUtils
|
||||
@ -26,6 +29,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
var searchFilter: String? = null
|
||||
|
||||
var itemsCaching = settings.getBoolean("items_caching", false)
|
||||
var offlineOverride = false
|
||||
|
||||
var apiMajorVersion = 0
|
||||
var badgeUnread = 0
|
||||
@ -45,14 +49,21 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
}
|
||||
|
||||
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
||||
// TODO: Check connectivity, use the updatedSince parameter
|
||||
val fetchedItems = api.getItems(displayedItems.type,
|
||||
// TODO: Use the updatedSince parameter
|
||||
var fetchedItems: List<SelfossModel.Item>? = null
|
||||
if (isNetworkAvailable()) {
|
||||
fetchedItems = api.getItems(
|
||||
displayedItems.type,
|
||||
settings.getString("prefer_api_items_number", "200").toInt(),
|
||||
offset = 0,
|
||||
tagFilter?.tag,
|
||||
sourceFilter?.id?.toLong(),
|
||||
searchFilter,
|
||||
null)
|
||||
null
|
||||
)
|
||||
} else {
|
||||
// TODO: Get items from the database
|
||||
}
|
||||
|
||||
if (fetchedItems != null) {
|
||||
items = ArrayList(fetchedItems)
|
||||
@ -61,15 +72,21 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
}
|
||||
|
||||
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
|
||||
// TODO: Check connectivity
|
||||
var fetchedItems: List<SelfossModel.Item>? = null
|
||||
if (isNetworkAvailable()) {
|
||||
val offset = items.size
|
||||
val fetchedItems = api.getItems(displayedItems.type,
|
||||
fetchedItems = api.getItems(
|
||||
displayedItems.type,
|
||||
settings.getString("prefer_api_items_number", "200").toInt(),
|
||||
offset,
|
||||
tagFilter?.tag,
|
||||
sourceFilter?.id?.toLong(),
|
||||
searchFilter,
|
||||
null)
|
||||
null
|
||||
)
|
||||
} else {
|
||||
// TODO: Get items from the database
|
||||
}
|
||||
|
||||
if (fetchedItems != null) {
|
||||
appendItems(fetchedItems)
|
||||
@ -77,8 +94,22 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
return items
|
||||
}
|
||||
|
||||
suspend fun allItems(itemType: ItemType): List<SelfossModel.Item>? =
|
||||
api.getItems(itemType.type, 200, 0, tagFilter?.tag, sourceFilter?.id?.toLong(), searchFilter, null)
|
||||
suspend fun allItems(itemType: ItemType): List<SelfossModel.Item>? {
|
||||
return if (isNetworkAvailable()) {
|
||||
api.getItems(
|
||||
itemType.type,
|
||||
200,
|
||||
0,
|
||||
tagFilter?.tag,
|
||||
sourceFilter?.id?.toLong(),
|
||||
searchFilter,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
// TODO: Provide an error message
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun appendItems(fetchedItems: List<SelfossModel.Item>) {
|
||||
// TODO: Store in DB if enabled by user
|
||||
@ -94,8 +125,8 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
}
|
||||
|
||||
suspend fun reloadBadges(): Boolean {
|
||||
// TODO: Check connectivity, calculate from DB
|
||||
var success = false
|
||||
if (isNetworkAvailable()) {
|
||||
val response = api.stats()
|
||||
if (response != null) {
|
||||
badgeUnread = response.unread
|
||||
@ -103,26 +134,43 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
badgeStarred = response.starred
|
||||
success = true
|
||||
}
|
||||
} else {
|
||||
// TODO: Compute badges from database
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun getTags(): List<SelfossModel.Tag>? {
|
||||
// TODO: Check success, store in DB
|
||||
return api.tags()
|
||||
// TODO: Store in DB
|
||||
return if (isNetworkAvailable()) {
|
||||
api.tags()
|
||||
} else {
|
||||
// TODO: Compute from database
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSpouts(): Map<String, SelfossModel.Spout>? {
|
||||
// TODO: Check success, store in DB
|
||||
return api.spouts()
|
||||
// TODO: Store in DB
|
||||
return if (isNetworkAvailable()) {
|
||||
api.spouts()
|
||||
} else {
|
||||
// TODO: Compute from database
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSources(): ArrayList<SelfossModel.Source>? {
|
||||
// TODO: Check success
|
||||
return api.sources()
|
||||
// TODO: Store in DB
|
||||
return if (isNetworkAvailable()) {
|
||||
api.sources()
|
||||
} else {
|
||||
// TODO: Compute from database
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun markAsRead(item: SelfossModel.Item): Boolean {
|
||||
// TODO: Check internet connection
|
||||
val success = markAsReadById(item.id)
|
||||
|
||||
if (success) {
|
||||
@ -131,10 +179,9 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun markAsReadById(id: Int): Boolean {
|
||||
// TODO: Check internet connection
|
||||
return api.markAsRead(id.toString())?.isSuccess == true
|
||||
}
|
||||
suspend fun markAsReadById(id: Int): Boolean =
|
||||
isNetworkAvailable() && api.markAsRead(id.toString())?.isSuccess == true
|
||||
|
||||
|
||||
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
|
||||
val success = unmarkAsReadById(item.id)
|
||||
@ -145,10 +192,8 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun unmarkAsReadById(id: Int): Boolean {
|
||||
// TODO: Check internet connection
|
||||
return api.unmarkAsRead(id.toString())?.isSuccess == true
|
||||
}
|
||||
suspend fun unmarkAsReadById(id: Int): Boolean =
|
||||
isNetworkAvailable() && api.unmarkAsRead(id.toString())?.isSuccess == true
|
||||
|
||||
suspend fun starr(item: SelfossModel.Item): Boolean {
|
||||
val success = starrById(item.id)
|
||||
@ -159,10 +204,8 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun starrById(id: Int): Boolean {
|
||||
// TODO: Check success, store in DB
|
||||
return api.starr(id.toString())?.isSuccess == true
|
||||
}
|
||||
suspend fun starrById(id: Int): Boolean =
|
||||
isNetworkAvailable() && api.starr(id.toString())?.isSuccess == true
|
||||
|
||||
suspend fun unstarr(item: SelfossModel.Item): Boolean {
|
||||
val success = unstarrById(item.id)
|
||||
@ -173,17 +216,15 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun unstarrById(id: Int): Boolean {
|
||||
// TODO: Check internet connection
|
||||
return api.unstarr(id.toString())?.isSuccess == true
|
||||
}
|
||||
suspend fun unstarrById(id: Int): Boolean =
|
||||
isNetworkAvailable() && api.unstarr(id.toString())?.isSuccess == true
|
||||
|
||||
|
||||
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
||||
// TODO: Check Internet connectivity, store in DB
|
||||
var success = false
|
||||
|
||||
val success = api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true
|
||||
|
||||
if (success) {
|
||||
if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true) {
|
||||
success = true
|
||||
for (item in items) {
|
||||
markAsReadLocally(item)
|
||||
}
|
||||
@ -230,46 +271,47 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
tags: String,
|
||||
filter: String
|
||||
): Boolean {
|
||||
// TODO: Check connectivity
|
||||
val response = api.createSourceForVersion(
|
||||
var response = false
|
||||
if (isNetworkAvailable()) {
|
||||
response = api.createSourceForVersion(
|
||||
title,
|
||||
url,
|
||||
spout,
|
||||
tags,
|
||||
filter,
|
||||
apiMajorVersion
|
||||
)
|
||||
)?.isSuccess == true
|
||||
}
|
||||
|
||||
return response != null
|
||||
return response
|
||||
}
|
||||
|
||||
suspend fun deleteSource(id: Int): Boolean {
|
||||
// TODO: Check connectivity, store in DB
|
||||
// TODO: Store in DB
|
||||
var success = false
|
||||
if (isNetworkAvailable()) {
|
||||
val response = api.deleteSource(id)
|
||||
if (response != null) {
|
||||
success = response.isSuccess
|
||||
}
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun updateRemote(): Boolean {
|
||||
// TODO: Handle connectivity issues
|
||||
val response = api.update()
|
||||
return response?.isSuccess ?: false
|
||||
}
|
||||
suspend fun updateRemote(): Boolean =
|
||||
isNetworkAvailable() && api.update()?.isSuccess == true
|
||||
|
||||
suspend fun login(): Boolean {
|
||||
var result = false
|
||||
if (isNetworkAvailable()) {
|
||||
try {
|
||||
val response = api.login()
|
||||
if (response != null && response.isSuccess) {
|
||||
result = true
|
||||
}
|
||||
result = response?.isSuccess == true
|
||||
} catch (cause: Throwable) {
|
||||
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.updateRemote")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@ -287,15 +329,33 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
}
|
||||
|
||||
private suspend fun updateApiVersion() {
|
||||
// TODO: Handle connectivity issues
|
||||
apiMajorVersion = settings.getInt("apiVersionMajor", 0)
|
||||
|
||||
if (isNetworkAvailable()) {
|
||||
val fetchedVersion = api.version()
|
||||
if (fetchedVersion != null) {
|
||||
apiMajorVersion = fetchedVersion.getApiMajorVersion()
|
||||
settings.putInt("apiVersionMajor", apiMajorVersion)
|
||||
} else {
|
||||
apiMajorVersion = settings.getInt("apiVersionMajor", 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride
|
||||
|
||||
// TODO: Start and stop the monitoring within Android and iOS code
|
||||
// This should be performed in the architecture specific code
|
||||
// However it is not possible on android due to the dash contained in the name of the library
|
||||
// com.github.ln-12:multiplatform-connectivity-status:1.1.0
|
||||
// https://github.com/ln-12/multiplatform-connectivity-status/issues/2
|
||||
fun startNetwork() {
|
||||
connectionMonitored = true
|
||||
connectivityStatus.start()
|
||||
}
|
||||
|
||||
fun stopNetwork() {
|
||||
connectionMonitored = false
|
||||
connectivityStatus.stop()
|
||||
}
|
||||
|
||||
// TODO: Handle offline actions
|
||||
}
|
Loading…
Reference in New Issue
Block a user