Compare commits
No commits in common. "afcc55e907786fb424b561c08935e087ce7c306c" and "1392e2a571550e136419bad00a2f8dce225cb075" have entirely different histories.
afcc55e907
...
1392e2a571
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
@ -10,7 +10,7 @@ Please read the guidelines before contributing, and follow them (or try to) when
|
|||||||
|
|
||||||
There are many ways to contribute to this project, you could [translate the app](https://crowdin.com/project/readerforselfoss), report bugs, request missing features, suggest enhancements and changes to existing ones. You also can improve the README with useful tips that could help the other users.
|
There are many ways to contribute to this project, you could [translate the app](https://crowdin.com/project/readerforselfoss), report bugs, request missing features, suggest enhancements and changes to existing ones. You also can improve the README with useful tips that could help the other users.
|
||||||
|
|
||||||
You can fork the repository, and [help me solve some issues](https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues)
|
You can fork the repository, and [help me solve some issues](https://github.com/aminecmi/ReaderforSelfoss/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://github.com/aminecmi/ReaderforSelfoss/issues)
|
||||||
|
|
||||||
### What I can't help you with.
|
### What I can't help you with.
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ Always check if the web version of your instance is working.
|
|||||||
|
|
||||||
### Pull requests
|
### Pull requests
|
||||||
|
|
||||||
* Don't create a PR for translations.
|
* Don't create a PR for translations. See [here](https://github.com/aminecmi/ReaderforSelfoss/pull/170#issuecomment-355715654) for an explanation why.
|
||||||
* Please ask before starting to work on an issue. I may be working on it, or someone else could be doing so.
|
* Please ask before starting to work on an issue. I may be working on it, or someone else could be doing so.
|
||||||
* Each pull request should implement **ONE** feature or bugfix. Keep in mind that you can submit as many PR as you want.
|
* Each pull request should implement **ONE** feature or bugfix. Keep in mind that you can submit as many PR as you want.
|
||||||
* Your code must be simple and clear enough to avoid using comments to explain what it does.
|
* Your code must be simple and clear enough to avoid using comments to explain what it does.
|
||||||
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -5,7 +5,7 @@
|
|||||||
- [ ] I have updated the documentation accordingly.
|
- [ ] I have updated the documentation accordingly.
|
||||||
- [ ] I have added tests to cover my changes.
|
- [ ] I have added tests to cover my changes.
|
||||||
- [ ] All new and existing tests passed.
|
- [ ] All new and existing tests passed.
|
||||||
- [ ] This is **NOT** translation related.
|
- [ ] This is **NOT** translation related. (See [here](https://github.com/aminecmi/ReaderforSelfoss/pull/170#issuecomment-355715654))
|
||||||
|
|
||||||
This closes issue #XXX
|
This closes issue #XXX
|
||||||
|
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -319,5 +319,3 @@ fabric.properties
|
|||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/gradle,kotlin,androidstudio,android,xcode,swift
|
# End of https://www.toptal.com/developers/gitignore/api/gradle,kotlin,androidstudio,android,xcode,swift
|
||||||
|
|
||||||
|
|
||||||
crowdin.properties
|
|
10
README.md
10
README.md
@ -22,15 +22,15 @@ If you are a user, you can still create new issues. I'll fix them when I can.
|
|||||||
|
|
||||||
1. **You'll have to have a Selfoss instance running.** You'll find everything you need to install it [here](https://selfoss.aditu.de/).
|
1. **You'll have to have a Selfoss instance running.** You'll find everything you need to install it [here](https://selfoss.aditu.de/).
|
||||||
|
|
||||||
2. Check the [Contribution guide](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md).
|
2. Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/blob/master/.github/CONTRIBUTING.md).
|
||||||
|
|
||||||
3. Build the project by following [these steps](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
|
3. Build the project by following [these steps](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/blob/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
|
||||||
|
|
||||||
## Useful links
|
## Useful links
|
||||||
|
|
||||||
- [Check what changed](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/CHANGELOG.md)
|
- [Check what changed](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/blob/master/CHANGELOG.md)
|
||||||
- [See what I'm doing](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/projects/1)
|
- [See what I'm doing](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/projects/1)
|
||||||
- [Create an issue, or request a new feature](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/issues)
|
- [Create an issue, or request a new feature](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/issues)
|
||||||
- [Help translation the app](https://crowdin.com/project/readerforselfoss)
|
- [Help translation the app](https://crowdin.com/project/readerforselfoss)
|
||||||
|
|
||||||
## Contributors (V1) (Alphabetical order) ❤️
|
## Contributors (V1) (Alphabetical order) ❤️
|
||||||
|
@ -181,31 +181,22 @@ dependencies {
|
|||||||
implementation("androidx.viewpager2:viewpager2:1.1.0-beta01")
|
implementation("androidx.viewpager2:viewpager2:1.1.0-beta01")
|
||||||
|
|
||||||
//Dependency Injection
|
//Dependency Injection
|
||||||
implementation("org.kodein.di:kodein-di:7.14.0")
|
implementation("org.kodein.di:kodein-di:7.12.0")
|
||||||
implementation("org.kodein.di:kodein-di-framework-android-x:7.14.0")
|
implementation("org.kodein.di:kodein-di-framework-android-x:7.12.0")
|
||||||
implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.14.0")
|
|
||||||
|
|
||||||
//Settings
|
//Settings
|
||||||
implementation("com.russhwolf:multiplatform-settings-no-arg:0.9")
|
implementation("com.russhwolf:multiplatform-settings-no-arg:0.9")
|
||||||
|
|
||||||
//Logging
|
|
||||||
implementation("io.github.aakira:napier:2.6.1")
|
|
||||||
|
|
||||||
//PhotoView
|
//PhotoView
|
||||||
implementation("com.github.chrisbanes:PhotoView:2.3.0")
|
implementation("com.github.chrisbanes:PhotoView:2.3.0")
|
||||||
|
|
||||||
implementation("androidx.core:core-ktx:1.8.0")
|
implementation("androidx.core:core-ktx:1.7.0")
|
||||||
|
|
||||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
|
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0")
|
||||||
implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1")
|
implementation("androidx.lifecycle:lifecycle-common-java8:2.4.0")
|
||||||
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")
|
implementation("androidx.room:room-ktx:2.4.0-beta01")
|
||||||
kapt("androidx.room:room-compiler:2.4.0-beta01")
|
kapt("androidx.room:room-compiler:2.4.0-beta01")
|
||||||
|
|
||||||
implementation("android.arch.work:work-runtime-ktx:1.0.1")
|
implementation("android.arch.work:work-runtime-ktx:1.0.1")
|
||||||
|
|
||||||
// Network information
|
|
||||||
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
|
|
||||||
}
|
}
|
@ -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.maybeShow
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge
|
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.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.toEntity
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toView
|
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.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||||
@ -126,6 +126,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
private var recyclerAdapter: RecyclerView.Adapter<*>? = null
|
private var recyclerAdapter: RecyclerView.Adapter<*>? = null
|
||||||
|
|
||||||
private var fromTabShortcut: Boolean = false
|
private var fromTabShortcut: Boolean = false
|
||||||
|
private var offlineShortcut: Boolean = false
|
||||||
|
|
||||||
private lateinit var tagsBadge: Map<Long, Int>
|
private lateinit var tagsBadge: Map<Long, Int>
|
||||||
|
|
||||||
@ -152,7 +153,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
val view = binding.root
|
val view = binding.root
|
||||||
|
|
||||||
fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1
|
fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1
|
||||||
repository.offlineOverride = intent.getBooleanExtra("startOffline", false)
|
offlineShortcut = intent.getBooleanExtra("startOffline", false)
|
||||||
|
|
||||||
if (fromTabShortcut) {
|
if (fromTabShortcut) {
|
||||||
elementsShown = ItemType.fromInt(intent.getIntExtra("shortcutTab", ItemType.UNREAD.position))
|
elementsShown = ItemType.fromInt(intent.getIntExtra("shortcutTab", ItemType.UNREAD.position))
|
||||||
@ -174,6 +175,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
AppDatabase::class.java, "selfoss-database"
|
AppDatabase::class.java, "selfoss-database"
|
||||||
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
|
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
|
||||||
|
|
||||||
|
|
||||||
customTabActivityHelper = CustomTabActivityHelper()
|
customTabActivityHelper = CustomTabActivityHelper()
|
||||||
|
|
||||||
dataBase = AndroidDeviceDatabase(applicationContext)
|
dataBase = AndroidDeviceDatabase(applicationContext)
|
||||||
@ -195,7 +197,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
R.color.refresh_progress_3
|
R.color.refresh_progress_3
|
||||||
)
|
)
|
||||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||||
repository.offlineOverride = false
|
offlineShortcut = false
|
||||||
lastFetchDone = false
|
lastFetchDone = false
|
||||||
handleDrawerItems()
|
handleDrawerItems()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@ -687,26 +689,30 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
var sources: List<SelfossModel.Source>?
|
var sources: List<SelfossModel.Source>?
|
||||||
|
|
||||||
fun sourcesApiCall() {
|
fun sourcesApiCall() {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut) && updateSources) {
|
||||||
val response = repository.getSources()
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
if (response != null) {
|
val response = repository.getSources()
|
||||||
sources = response
|
if (response != null) {
|
||||||
val apiDrawerData = DrawerData(tags, sources)
|
sources = response
|
||||||
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) {
|
val apiDrawerData = DrawerData(tags, sources)
|
||||||
handleDrawerData(apiDrawerData)
|
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) {
|
||||||
}
|
handleDrawerData(apiDrawerData)
|
||||||
} else {
|
}
|
||||||
val apiDrawerData = DrawerData(tags, null)
|
} else {
|
||||||
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) {
|
val apiDrawerData = DrawerData(tags, null)
|
||||||
handleDrawerData(apiDrawerData)
|
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) {
|
||||||
|
handleDrawerData(apiDrawerData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut) && updateSources) {
|
||||||
tags = repository.getTags()
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
sourcesApiCall()
|
tags = repository.getTags()
|
||||||
|
sourcesApiCall()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -938,8 +944,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
private fun reloadBadges() {
|
private fun reloadBadges() {
|
||||||
if (displayUnreadCount || displayAllCount) {
|
if (displayUnreadCount || displayAllCount) {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
repository.reloadBadges()
|
if (applicationContext.isNetworkAvailable()) {
|
||||||
reloadBadgeContent()
|
repository.reloadBadges()
|
||||||
|
reloadBadgeContent()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1013,56 +1021,61 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.refresh -> {
|
R.id.refresh -> {
|
||||||
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
|
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
||||||
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
|
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
|
||||||
// TODO: Use Dispatchers.IO
|
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
// TODO: Use Dispatchers.IO
|
||||||
val updatedRemote = repository.updateRemote()
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
if (updatedRemote) {
|
val updatedRemote = repository.updateRemote()
|
||||||
// TODO: Send toast messages from the repository
|
if (updatedRemote) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@HomeActivity,
|
this@HomeActivity,
|
||||||
R.string.refresh_success_response, Toast.LENGTH_LONG
|
R.string.refresh_success_response, Toast.LENGTH_LONG
|
||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@HomeActivity,
|
this@HomeActivity,
|
||||||
R.string.refresh_failer_message,
|
R.string.refresh_failer_message,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
R.id.readAll -> {
|
R.id.readAll -> {
|
||||||
if (elementsShown == ItemType.UNREAD) {
|
if (elementsShown == ItemType.UNREAD) {
|
||||||
needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
|
needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
|
||||||
binding.swipeRefreshLayout.isRefreshing = true
|
binding.swipeRefreshLayout.isRefreshing = true
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
||||||
val success = repository.markAllAsRead(items)
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
if (success) {
|
val success = repository.markAllAsRead(items)
|
||||||
Toast.makeText(
|
if (success) {
|
||||||
this@HomeActivity,
|
Toast.makeText(
|
||||||
R.string.all_posts_read,
|
this@HomeActivity,
|
||||||
Toast.LENGTH_SHORT
|
R.string.all_posts_read,
|
||||||
).show()
|
Toast.LENGTH_SHORT
|
||||||
tabNewBadge.removeBadge()
|
).show()
|
||||||
|
tabNewBadge.removeBadge()
|
||||||
|
|
||||||
handleDrawerItems()
|
handleDrawerItems()
|
||||||
|
|
||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@HomeActivity,
|
this@HomeActivity,
|
||||||
R.string.all_posts_not_read,
|
R.string.all_posts_not_read,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
|
}
|
||||||
|
handleListResult()
|
||||||
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
}
|
}
|
||||||
handleListResult()
|
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1114,15 +1127,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
||||||
val actions = db.actionsDao().actions()
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
val actions = db.actionsDao().actions()
|
||||||
|
|
||||||
actions.forEach { action ->
|
actions.forEach { action ->
|
||||||
when {
|
when {
|
||||||
action.read -> doAndReportOnFail(repository.markAsReadById(action.articleId.toInt()), action)
|
action.read -> doAndReportOnFail(repository.markAsReadById(action.articleId.toInt()), action)
|
||||||
action.unread -> doAndReportOnFail(repository.unmarkAsReadById(action.articleId.toInt()), action)
|
action.unread -> doAndReportOnFail(repository.unmarkAsReadById(action.articleId.toInt()), action)
|
||||||
action.starred -> doAndReportOnFail(repository.starrById(action.articleId.toInt()), action)
|
action.starred -> doAndReportOnFail(repository.starrById(action.articleId.toInt()), action)
|
||||||
action.unstarred -> doAndReportOnFail(repository.unstarrById(action.articleId.toInt()), action)
|
action.unstarred -> doAndReportOnFail(repository.unstarrById(action.articleId.toInt()), action)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
|
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 bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
@ -201,13 +202,15 @@ class LoginActivity() : AppCompatActivity(), DIAware {
|
|||||||
|
|
||||||
repository.refreshLoginInformation(url, login, password, httpLogin, httpPassword, isWithSelfSignedCert)
|
repository.refreshLoginInformation(url, login, password, httpLogin, httpPassword, isWithSelfSignedCert)
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
if (this@LoginActivity.isNetworkAvailable(this@LoginActivity.findViewById(R.id.loginForm))) {
|
||||||
val result = repository.login()
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
if (result) {
|
val result = repository.login()
|
||||||
goToMain()
|
if (result) {
|
||||||
} else {
|
goToMain()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
} else {
|
||||||
preferenceError(Exception("Not success"))
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
|
preferenceError(Exception("Not success"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,50 +7,32 @@ import android.graphics.drawable.Drawable
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.ImageView
|
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.multidex.MultiDexApplication
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import bou.amine.apps.readerforselfossv2.DI.networkModule
|
import bou.amine.apps.readerforselfossv2.DI.networkModule
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
|
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 bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.ftinc.scoop.Scoop
|
import com.ftinc.scoop.Scoop
|
||||||
import com.github.ln_12.library.ConnectivityStatus
|
|
||||||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
||||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||||
import com.russhwolf.settings.Settings
|
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.*
|
import org.kodein.di.*
|
||||||
|
|
||||||
class MyApp : MultiDexApplication(), DIAware {
|
class MyApp : MultiDexApplication(), DIAware {
|
||||||
|
|
||||||
override val di by DI.lazy {
|
override val di by DI.lazy {
|
||||||
import(networkModule)
|
import(networkModule)
|
||||||
bind<Repository>() with singleton { Repository(instance(), instance(), connectivityStatus) }
|
bind<Repository>() with singleton { Repository(instance(), instance()) }
|
||||||
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
|
|
||||||
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val repository: Repository by instance()
|
|
||||||
private val viewModel: AppViewModel by instance()
|
|
||||||
private val connectivityStatus: ConnectivityStatus by instance()
|
|
||||||
private lateinit var config: Config
|
private lateinit var config: Config
|
||||||
private lateinit var settings : Settings
|
private lateinit var settings : Settings
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
Napier.base(DebugAntilog())
|
|
||||||
config = Config()
|
config = Config()
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
|
|
||||||
@ -61,18 +43,6 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
tryToHandleBug()
|
tryToHandleBug()
|
||||||
|
|
||||||
handleNotificationChannels()
|
handleNotificationChannels()
|
||||||
|
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository))
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
viewModel.toastMessageProvider.collect { toastMessage ->
|
|
||||||
Toast.makeText(
|
|
||||||
applicationContext,
|
|
||||||
toastMessage,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleNotificationChannels() {
|
private fun handleNotificationChannels() {
|
||||||
@ -132,19 +102,4 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppLifeCycleObserver(val connectivityStatus: ConnectivityStatus, val repository: Repository) : DefaultLifecycleObserver {
|
|
||||||
|
|
||||||
override fun onResume(owner: LifecycleOwner) {
|
|
||||||
super.onResume(owner)
|
|
||||||
repository.connectionMonitored = true
|
|
||||||
connectivityStatus.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause(owner: LifecycleOwner) {
|
|
||||||
repository.connectionMonitored = false
|
|
||||||
connectivityStatus.stop()
|
|
||||||
super.onPause(owner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -10,6 +10,7 @@ import bou.amine.apps.readerforselfossv2.android.adapters.SourcesListAdapter
|
|||||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySourcesBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySourcesBinding
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||||
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
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.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
import com.ftinc.scoop.Scoop
|
import com.ftinc.scoop.Scoop
|
||||||
@ -63,28 +64,30 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
|||||||
binding.recyclerView.setHasFixedSize(true)
|
binding.recyclerView.setHasFixedSize(true)
|
||||||
binding.recyclerView.layoutManager = mLayoutManager
|
binding.recyclerView.layoutManager = mLayoutManager
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
if (this@SourcesActivity.isNetworkAvailable(binding.recyclerView)) {
|
||||||
val response = repository.getSources()
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
if (response != null) {
|
val response = repository.getSources()
|
||||||
items = response
|
if (response != null) {
|
||||||
val mAdapter = SourcesListAdapter(
|
items = response
|
||||||
this@SourcesActivity, items
|
val mAdapter = SourcesListAdapter(
|
||||||
)
|
this@SourcesActivity, items
|
||||||
binding.recyclerView.adapter = mAdapter
|
)
|
||||||
mAdapter.notifyDataSetChanged()
|
binding.recyclerView.adapter = mAdapter
|
||||||
if (items.isEmpty()) {
|
mAdapter.notifyDataSetChanged()
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
Toast.makeText(
|
||||||
|
this@SourcesActivity,
|
||||||
|
R.string.nothing_here,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@SourcesActivity,
|
this@SourcesActivity,
|
||||||
R.string.nothing_here,
|
R.string.cant_get_sources,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Toast.makeText(
|
|
||||||
this@SourcesActivity,
|
|
||||||
R.string.cant_get_sources,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import bou.amine.apps.readerforselfossv2.android.utils.*
|
|||||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
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.bitmapCenterCrop
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
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.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
@ -109,20 +110,22 @@ class ItemCardAdapter(
|
|||||||
|
|
||||||
binding.favButton.setOnClickListener {
|
binding.favButton.setOnClickListener {
|
||||||
val item = items[bindingAdapterPosition]
|
val item = items[bindingAdapterPosition]
|
||||||
if (item.starred) {
|
if (c.isNetworkAvailable()) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
if (item.starred) {
|
||||||
repository.unstarr(item)
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
// TODO: Handle failure
|
repository.unstarr(item)
|
||||||
|
// TODO: Handle failure
|
||||||
|
}
|
||||||
|
item.starred = false
|
||||||
|
binding.favButton.isSelected = false
|
||||||
|
} else {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
repository.starr(item)
|
||||||
|
// TODO: Handle failure
|
||||||
|
}
|
||||||
|
item.starred = true
|
||||||
|
binding.favButton.isSelected = true
|
||||||
}
|
}
|
||||||
item.starred = false
|
|
||||||
binding.favButton.isSelected = false
|
|
||||||
} else {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
repository.starr(item)
|
|
||||||
// TODO: Handle failure
|
|
||||||
}
|
|
||||||
item.starred = true
|
|
||||||
binding.favButton.isSelected = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import bou.amine.apps.readerforselfossv2.android.model.getIcon
|
|||||||
import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded
|
import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
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.android.utils.toTextDrawableString
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
@ -77,19 +78,21 @@ class SourcesListAdapter(
|
|||||||
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
|
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
|
||||||
|
|
||||||
deleteBtn.setOnClickListener {
|
deleteBtn.setOnClickListener {
|
||||||
val (id) = items[adapterPosition]
|
if (c.isNetworkAvailable(null)) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
val (id) = items[adapterPosition]
|
||||||
val successfullyDeletedSource = repository.deleteSource(id)
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
if (successfullyDeletedSource) {
|
val successfullyDeletedSource = repository.deleteSource(id)
|
||||||
items.removeAt(adapterPosition)
|
if (successfullyDeletedSource) {
|
||||||
notifyItemRemoved(adapterPosition)
|
items.removeAt(adapterPosition)
|
||||||
notifyItemRangeChanged(adapterPosition, itemCount)
|
notifyItemRemoved(adapterPosition)
|
||||||
} else {
|
notifyItemRangeChanged(adapterPosition, itemCount)
|
||||||
Toast.makeText(
|
} else {
|
||||||
app,
|
Toast.makeText(
|
||||||
R.string.can_delete_source,
|
app,
|
||||||
Toast.LENGTH_SHORT
|
R.string.can_delete_source,
|
||||||
).show()
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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_2_3
|
||||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
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.Config
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
|
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||||
@ -44,62 +44,67 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con
|
|||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
val settings = Settings()
|
val settings = Settings()
|
||||||
val periodicRefresh = settings.getBoolean("periodic_refresh", false)
|
val periodicRefresh = settings.getBoolean("periodic_refresh", false)
|
||||||
if (periodicRefresh && isNetworkAccessible(context)) {
|
if (periodicRefresh) {
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
if (context.isNetworkAvailable()) {
|
||||||
val notificationManager =
|
|
||||||
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
||||||
|
|
||||||
val notification =
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
NotificationCompat.Builder(applicationContext, Config.syncChannelId)
|
val notificationManager =
|
||||||
.setContentTitle(context.getString(R.string.loading_notification_title))
|
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
.setContentText(context.getString(R.string.loading_notification_text))
|
|
||||||
.setOngoing(true)
|
|
||||||
.setPriority(PRIORITY_LOW)
|
|
||||||
.setChannelId(Config.syncChannelId)
|
|
||||||
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
|
|
||||||
|
|
||||||
notificationManager.notify(1, notification.build())
|
val notification =
|
||||||
|
NotificationCompat.Builder(applicationContext, Config.syncChannelId)
|
||||||
|
.setContentTitle(context.getString(R.string.loading_notification_title))
|
||||||
|
.setContentText(context.getString(R.string.loading_notification_text))
|
||||||
|
.setOngoing(true)
|
||||||
|
.setPriority(PRIORITY_LOW)
|
||||||
|
.setChannelId(Config.syncChannelId)
|
||||||
|
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
|
||||||
|
|
||||||
val notifyNewItems = settings.getBoolean("notify_new_items", false)
|
notificationManager.notify(1, notification.build())
|
||||||
|
|
||||||
db = Room.databaseBuilder(
|
val notifyNewItems = settings.getBoolean("notify_new_items", false)
|
||||||
applicationContext,
|
|
||||||
AppDatabase::class.java, "selfoss-database"
|
|
||||||
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3)
|
|
||||||
.addMigrations(MIGRATION_3_4).build()
|
|
||||||
|
|
||||||
val actions = db.actionsDao().actions()
|
db = Room.databaseBuilder(
|
||||||
|
applicationContext,
|
||||||
|
AppDatabase::class.java, "selfoss-database"
|
||||||
|
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3)
|
||||||
|
.addMigrations(MIGRATION_3_4).build()
|
||||||
|
|
||||||
actions.forEach { action ->
|
val actions = db.actionsDao().actions()
|
||||||
when {
|
|
||||||
action.read -> doAndReportOnFail(
|
actions.forEach { action ->
|
||||||
repository.markAsReadById(action.articleId.toInt()),
|
when {
|
||||||
action
|
action.read -> doAndReportOnFail(
|
||||||
)
|
repository.markAsReadById(action.articleId.toInt()),
|
||||||
action.unread -> doAndReportOnFail(
|
action
|
||||||
repository.unmarkAsReadById(action.articleId.toInt()),
|
)
|
||||||
action
|
action.unread -> doAndReportOnFail(
|
||||||
)
|
repository.unmarkAsReadById(action.articleId.toInt()),
|
||||||
action.starred -> doAndReportOnFail(
|
action
|
||||||
repository.starrById(action.articleId.toInt()),
|
)
|
||||||
action
|
action.starred -> doAndReportOnFail(
|
||||||
)
|
repository.starrById(action.articleId.toInt()),
|
||||||
action.unstarred -> doAndReportOnFail(
|
action
|
||||||
repository.unstarrById(action.articleId.toInt()),
|
)
|
||||||
action
|
action.unstarred -> doAndReportOnFail(
|
||||||
)
|
repository.unstarrById(action.articleId.toInt()),
|
||||||
|
action
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
launch {
|
if (context.isNetworkAvailable()) {
|
||||||
try {
|
launch {
|
||||||
val newItems = repository.allItems(ItemType.UNREAD)
|
try {
|
||||||
handleNewItemsNotification(newItems, notifyNewItems, notificationManager)
|
val newItems = repository.allItems(ItemType.UNREAD)
|
||||||
val readItems = repository.allItems(ItemType.ALL)
|
handleNewItemsNotification(newItems, notifyNewItems, notificationManager)
|
||||||
val starredItems = repository.allItems(ItemType.STARRED)
|
val readItems = repository.allItems(ItemType.ALL)
|
||||||
// TODO: save all to DB
|
val starredItems = repository.allItems(ItemType.STARRED)
|
||||||
} catch (e: Throwable) {}
|
// TODO: save all to DB
|
||||||
|
} catch (e: Throwable) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import bou.amine.apps.readerforselfossv2.android.utils.*
|
|||||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
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.getBitmapInputStream
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
|
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.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
||||||
@ -275,9 +276,9 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) {
|
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) {
|
||||||
if (repository.isNetworkAvailable()) {
|
if ((context != null && requireContext().isNetworkAvailable(null)) || context == null) {
|
||||||
binding.progressBar.visibility = View.VISIBLE
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
val parser = MercuryApi()
|
val parser = MercuryApi()
|
||||||
|
|
||||||
parser.parseUrl(url).enqueue(
|
parser.parseUrl(url).enqueue(
|
||||||
object : Callback<ParsedContent> {
|
object : Callback<ParsedContent> {
|
||||||
@ -316,10 +317,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
Glide
|
Glide
|
||||||
.with(requireContext())
|
.with(requireContext())
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.loadMaybeBasicAuth(
|
.loadMaybeBasicAuth(config, response.body()!!.lead_image_url.orEmpty())
|
||||||
config,
|
|
||||||
response.body()!!.lead_image_url.orEmpty()
|
|
||||||
)
|
|
||||||
.apply(RequestOptions.fitCenterTransform())
|
.apply(RequestOptions.fitCenterTransform())
|
||||||
.into(binding.imageView)
|
.into(binding.imageView)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -28,13 +28,13 @@ class Config {
|
|||||||
companion object {
|
companion object {
|
||||||
const val settingsName = "paramsselfoss"
|
const val settingsName = "paramsselfoss"
|
||||||
|
|
||||||
const val feedbackEmail = "aminecmi@pm.me.com"
|
const val feedbackEmail = "aminecmi@gmail.com"
|
||||||
|
|
||||||
const val translationUrl = "https://crwd.in/readerforselfoss"
|
const val translationUrl = "https://crwd.in/readerforselfoss"
|
||||||
|
|
||||||
const val sourceUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform"
|
const val sourceUrl = "https://github.com/aminecmi/ReaderforSelfoss"
|
||||||
|
|
||||||
const val trackerUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
const val trackerUrl = "https://github.com/aminecmi/ReaderforSelfoss/issues"
|
||||||
|
|
||||||
const val syncChannelId = "sync-channel-id"
|
const val syncChannelId = "sync-channel-id"
|
||||||
|
|
||||||
|
@ -1,14 +1,52 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.utils.network
|
package bou.amine.apps.readerforselfossv2.android.utils.network
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import android.os.Build
|
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
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
|
||||||
|
var snackBarShown = false
|
||||||
|
var view: View? = null
|
||||||
lateinit var s: Snackbar
|
lateinit var s: Snackbar
|
||||||
|
|
||||||
fun isNetworkAccessible(context: Context): Boolean {
|
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 {
|
||||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||||
<string name="no_network_connectivity">Sense connexió!</string>
|
<string name="no_network_connectivity">Sense connexió!</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">Sincronitza els articles</string>
|
<string name="pref_switch_periodic_refresh">Sincronitza els articles</string>
|
||||||
<string name="pref_switch_periodic_refresh_off">Els articles no se sincronitzaran en segon pla</string>
|
<string name="pref_switch_periodic_refresh_off">Els articles no se sincronitzaran en segon pla</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Els articles se sincronitzaran periòdicament</string>
|
<string name="pref_switch_periodic_refresh_on">Els articles se sincronitzaran periòdicament</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||||
<string name="no_network_connectivity">Nicht verbunden !</string>
|
<string name="no_network_connectivity">Nicht verbunden !</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">Synchronisiere Artikel</string>
|
<string name="pref_switch_periodic_refresh">Synchronisiere Artikel</string>
|
||||||
<string name="pref_switch_periodic_refresh_off">Artikel werden nicht im Hintergrund synchronisiert</string>
|
<string name="pref_switch_periodic_refresh_off">Artikel werden nicht im Hintergrund synchronisiert</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Die Artikel werden regelmäßig synchronisiert</string>
|
<string name="pref_switch_periodic_refresh_on">Die Artikel werden regelmäßig synchronisiert</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||||
<string name="no_network_connectivity">Sin conexión!</string>
|
<string name="no_network_connectivity">Sin conexión!</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">Sincronizar artículos</string>
|
<string name="pref_switch_periodic_refresh">Sincronizar artículos</string>
|
||||||
<string name="pref_switch_periodic_refresh_off">Los artículos no se sincronizarán en segundo plano</string>
|
<string name="pref_switch_periodic_refresh_off">Los artículos no se sincronizarán en segundo plano</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Los artículos se sincronizarán periódicamente</string>
|
<string name="pref_switch_periodic_refresh_on">Los artículos se sincronizarán periódicamente</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="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="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">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_off">Articles will not be synced in the background</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Vérifier les nouvelles sources et tags</string>
|
<string name="pref_switch_update_sources">Vérifier les nouvelles sources et tags</string>
|
||||||
<string name="pref_switch_update_sources_summary">Désactivez cette option si votre serveur reçoit trop de requêtes.</string>
|
<string name="pref_switch_update_sources_summary">Désactivez cette option si votre serveur reçoit trop de requêtes.</string>
|
||||||
<string name="no_network_connectivity">Hors connexion !</string>
|
<string name="no_network_connectivity">Hors connexion !</string>
|
||||||
<string name="network_connectivity_lost">"Connexion au réseau perdue"</string>
|
|
||||||
<string name="network_connectivity_retrieved">"Connexion réseau de nouveau disponible"</string>
|
|
||||||
<string name="pref_switch_periodic_refresh">Synchroniser les articles</string>
|
<string name="pref_switch_periodic_refresh">Synchroniser les articles</string>
|
||||||
<string name="pref_switch_periodic_refresh_off">Les articles ne seront pas synchronisés en arrière plan</string>
|
<string name="pref_switch_periodic_refresh_off">Les articles ne seront pas synchronisés en arrière plan</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Articles seront périodiquement synchronisées</string>
|
<string name="pref_switch_periodic_refresh_on">Articles seront périodiquement synchronisées</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Comproba novas fontes e etiquetas</string>
|
<string name="pref_switch_update_sources">Comproba novas fontes e etiquetas</string>
|
||||||
<string name="pref_switch_update_sources_summary">Deshabilita isto se o teu servidor está recibindo demasiadas peticións de base de datos.</string>
|
<string name="pref_switch_update_sources_summary">Deshabilita isto se o teu servidor está recibindo demasiadas peticións de base de datos.</string>
|
||||||
<string name="no_network_connectivity">Non conectado!</string>
|
<string name="no_network_connectivity">Non conectado!</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">Sincronizar artigos</string>
|
<string name="pref_switch_periodic_refresh">Sincronizar artigos</string>
|
||||||
<string name="pref_switch_periodic_refresh_off">Os artigos non se sincronizarán coa aplicación de fondo</string>
|
<string name="pref_switch_periodic_refresh_off">Os artigos non se sincronizarán coa aplicación de fondo</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Os artigos sincronizaranse periódicamente</string>
|
<string name="pref_switch_periodic_refresh_on">Os artigos sincronizaranse periódicamente</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="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="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">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_off">Articles will not be synced in the background</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="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="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">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_off">Articles will not be synced in the background</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="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="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">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_off">Articles will not be synced in the background</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="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="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">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_off">Articles will not be synced in the background</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="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="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">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_off">Articles will not be synced in the background</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="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="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">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_off">Articles will not be synced in the background</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="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="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">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_off">Articles will not be synced in the background</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="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="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">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_off">Articles will not be synced in the background</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">检查新来源和标签</string>
|
<string name="pref_switch_update_sources">检查新来源和标签</string>
|
||||||
<string name="pref_switch_update_sources_summary">如果你的服务器接收过多的数据库查询,请禁用此功能。</string>
|
<string name="pref_switch_update_sources_summary">如果你的服务器接收过多的数据库查询,请禁用此功能。</string>
|
||||||
<string name="no_network_connectivity">未连接!</string>
|
<string name="no_network_connectivity">未连接!</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">同步文章</string>
|
<string name="pref_switch_periodic_refresh">同步文章</string>
|
||||||
<string name="pref_switch_periodic_refresh_off">文章将不会在后台同步</string>
|
<string name="pref_switch_periodic_refresh_off">文章将不会在后台同步</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">将定期同步文章</string>
|
<string name="pref_switch_periodic_refresh_on">将定期同步文章</string>
|
||||||
|
@ -142,8 +142,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="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="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">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_off">Articles will not be synced in the background</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||||
|
@ -143,8 +143,6 @@
|
|||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<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="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="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">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_off">Articles will not be synced in the background</string>
|
||||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||||
|
@ -55,7 +55,7 @@ redirect_from: "/ReaderforSelfoss-multiplatform/"
|
|||||||
|
|
||||||
|
|
||||||
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body id="main">
|
<body id="main">
|
||||||
@ -68,9 +68,9 @@ redirect_from: "/ReaderforSelfoss-multiplatform/"
|
|||||||
<div id="links">
|
<div id="links">
|
||||||
|
|
||||||
|
|
||||||
<a class="github-button" href="https://gitea.amine-louveau.fr/Louvorg/readerforselfoss-multiplatform" data-size="large" aria-label="Star aminecmi/readerforselfoss-multiplatform on GitHub">Star</a>
|
<a class="github-button" href="https://github.com/aminecmi/readerforselfoss-multiplatform" data-size="large" aria-label="Star aminecmi/readerforselfoss-multiplatform on GitHub">Star</a>
|
||||||
</div>
|
</div>
|
||||||
<meta itemprop="url" content="https://gitea.amine-louveau.fr/Louvorg/readerforselfoss-multiplatform">
|
<meta itemprop="url" content="https://github.com/aminecmi/readerforselfoss-multiplatform">
|
||||||
<meta itemprop="applicationCategory" content="News & Magazines">
|
<meta itemprop="applicationCategory" content="News & Magazines">
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@ -36,9 +36,6 @@ kotlin {
|
|||||||
|
|
||||||
//Logging
|
//Logging
|
||||||
implementation("io.github.aakira:napier:2.6.1")
|
implementation("io.github.aakira:napier:2.6.1")
|
||||||
|
|
||||||
// Network information
|
|
||||||
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
|
@ -5,19 +5,16 @@ import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
|||||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||||
import com.github.ln_12.library.ConnectivityStatus
|
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
import io.github.aakira.napier.Napier
|
import io.github.aakira.napier.Napier
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService, val connectivityStatus: ConnectivityStatus) {
|
class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService) {
|
||||||
val settings = Settings()
|
val settings = Settings()
|
||||||
|
|
||||||
var items = ArrayList<SelfossModel.Item>()
|
var items = ArrayList<SelfossModel.Item>()
|
||||||
val isConnectionAvailable = connectivityStatus.isNetworkConnected
|
|
||||||
var connectionMonitored = false
|
|
||||||
|
|
||||||
var baseUrl = apiDetails.getBaseUrl()
|
var baseUrl = apiDetails.getBaseUrl()
|
||||||
lateinit var dateUtils: DateUtils
|
lateinit var dateUtils: DateUtils
|
||||||
@ -29,7 +26,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
var searchFilter: String? = null
|
var searchFilter: String? = null
|
||||||
|
|
||||||
var itemsCaching = settings.getBoolean("items_caching", false)
|
var itemsCaching = settings.getBoolean("items_caching", false)
|
||||||
var offlineOverride = false
|
|
||||||
|
|
||||||
var apiMajorVersion = 0
|
var apiMajorVersion = 0
|
||||||
var badgeUnread = 0
|
var badgeUnread = 0
|
||||||
@ -49,21 +45,14 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
||||||
// TODO: Use the updatedSince parameter
|
// TODO: Check connectivity, use the updatedSince parameter
|
||||||
var fetchedItems: List<SelfossModel.Item>? = null
|
val fetchedItems = api.getItems(displayedItems.type,
|
||||||
if (isNetworkAvailable()) {
|
settings.getString("prefer_api_items_number", "200").toInt(),
|
||||||
fetchedItems = api.getItems(
|
offset = 0,
|
||||||
displayedItems.type,
|
tagFilter?.tag,
|
||||||
settings.getString("prefer_api_items_number", "200").toInt(),
|
sourceFilter?.id?.toLong(),
|
||||||
offset = 0,
|
searchFilter,
|
||||||
tagFilter?.tag,
|
null)
|
||||||
sourceFilter?.id?.toLong(),
|
|
||||||
searchFilter,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// TODO: Get items from the database
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fetchedItems != null) {
|
if (fetchedItems != null) {
|
||||||
items = ArrayList(fetchedItems)
|
items = ArrayList(fetchedItems)
|
||||||
@ -72,21 +61,15 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
|
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
|
||||||
var fetchedItems: List<SelfossModel.Item>? = null
|
// TODO: Check connectivity
|
||||||
if (isNetworkAvailable()) {
|
val offset = items.size
|
||||||
val offset = items.size
|
val fetchedItems = api.getItems(displayedItems.type,
|
||||||
fetchedItems = api.getItems(
|
settings.getString("prefer_api_items_number", "200").toInt(),
|
||||||
displayedItems.type,
|
offset,
|
||||||
settings.getString("prefer_api_items_number", "200").toInt(),
|
tagFilter?.tag,
|
||||||
offset,
|
sourceFilter?.id?.toLong(),
|
||||||
tagFilter?.tag,
|
searchFilter,
|
||||||
sourceFilter?.id?.toLong(),
|
null)
|
||||||
searchFilter,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// TODO: Get items from the database
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fetchedItems != null) {
|
if (fetchedItems != null) {
|
||||||
appendItems(fetchedItems)
|
appendItems(fetchedItems)
|
||||||
@ -94,22 +77,8 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun allItems(itemType: ItemType): List<SelfossModel.Item>? {
|
suspend fun allItems(itemType: ItemType): List<SelfossModel.Item>? =
|
||||||
return if (isNetworkAvailable()) {
|
api.getItems(itemType.type, 200, 0, tagFilter?.tag, sourceFilter?.id?.toLong(), searchFilter, null)
|
||||||
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>) {
|
private fun appendItems(fetchedItems: List<SelfossModel.Item>) {
|
||||||
// TODO: Store in DB if enabled by user
|
// TODO: Store in DB if enabled by user
|
||||||
@ -125,52 +94,35 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun reloadBadges(): Boolean {
|
suspend fun reloadBadges(): Boolean {
|
||||||
|
// TODO: Check connectivity, calculate from DB
|
||||||
var success = false
|
var success = false
|
||||||
if (isNetworkAvailable()) {
|
val response = api.stats()
|
||||||
val response = api.stats()
|
if (response != null) {
|
||||||
if (response != null) {
|
badgeUnread = response.unread
|
||||||
badgeUnread = response.unread
|
badgeAll = response.total
|
||||||
badgeAll = response.total
|
badgeStarred = response.starred
|
||||||
badgeStarred = response.starred
|
success = true
|
||||||
success = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO: Compute badges from database
|
|
||||||
}
|
}
|
||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getTags(): List<SelfossModel.Tag>? {
|
suspend fun getTags(): List<SelfossModel.Tag>? {
|
||||||
// TODO: Store in DB
|
// TODO: Check success, store in DB
|
||||||
return if (isNetworkAvailable()) {
|
return api.tags()
|
||||||
api.tags()
|
|
||||||
} else {
|
|
||||||
// TODO: Compute from database
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSpouts(): Map<String, SelfossModel.Spout>? {
|
suspend fun getSpouts(): Map<String, SelfossModel.Spout>? {
|
||||||
// TODO: Store in DB
|
// TODO: Check success, store in DB
|
||||||
return if (isNetworkAvailable()) {
|
return api.spouts()
|
||||||
api.spouts()
|
|
||||||
} else {
|
|
||||||
// TODO: Compute from database
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSources(): ArrayList<SelfossModel.Source>? {
|
suspend fun getSources(): ArrayList<SelfossModel.Source>? {
|
||||||
// TODO: Store in DB
|
// TODO: Check success
|
||||||
return if (isNetworkAvailable()) {
|
return api.sources()
|
||||||
api.sources()
|
|
||||||
} else {
|
|
||||||
// TODO: Compute from database
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markAsRead(item: SelfossModel.Item): Boolean {
|
suspend fun markAsRead(item: SelfossModel.Item): Boolean {
|
||||||
|
// TODO: Check internet connection
|
||||||
val success = markAsReadById(item.id)
|
val success = markAsReadById(item.id)
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -179,9 +131,10 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markAsReadById(id: Int): Boolean =
|
suspend fun markAsReadById(id: Int): Boolean {
|
||||||
isNetworkAvailable() && api.markAsRead(id.toString())?.isSuccess == true
|
// TODO: Check internet connection
|
||||||
|
return api.markAsRead(id.toString())?.isSuccess == true
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
|
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
|
||||||
val success = unmarkAsReadById(item.id)
|
val success = unmarkAsReadById(item.id)
|
||||||
@ -192,8 +145,10 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unmarkAsReadById(id: Int): Boolean =
|
suspend fun unmarkAsReadById(id: Int): Boolean {
|
||||||
isNetworkAvailable() && api.unmarkAsRead(id.toString())?.isSuccess == true
|
// TODO: Check internet connection
|
||||||
|
return api.unmarkAsRead(id.toString())?.isSuccess == true
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun starr(item: SelfossModel.Item): Boolean {
|
suspend fun starr(item: SelfossModel.Item): Boolean {
|
||||||
val success = starrById(item.id)
|
val success = starrById(item.id)
|
||||||
@ -204,8 +159,10 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun starrById(id: Int): Boolean =
|
suspend fun starrById(id: Int): Boolean {
|
||||||
isNetworkAvailable() && api.starr(id.toString())?.isSuccess == true
|
// TODO: Check success, store in DB
|
||||||
|
return api.starr(id.toString())?.isSuccess == true
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun unstarr(item: SelfossModel.Item): Boolean {
|
suspend fun unstarr(item: SelfossModel.Item): Boolean {
|
||||||
val success = unstarrById(item.id)
|
val success = unstarrById(item.id)
|
||||||
@ -216,15 +173,17 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unstarrById(id: Int): Boolean =
|
suspend fun unstarrById(id: Int): Boolean {
|
||||||
isNetworkAvailable() && api.unstarr(id.toString())?.isSuccess == true
|
// TODO: Check internet connection
|
||||||
|
return api.unstarr(id.toString())?.isSuccess == true
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
||||||
var success = false
|
// TODO: Check Internet connectivity, store in DB
|
||||||
|
|
||||||
if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true) {
|
val success = api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true
|
||||||
success = true
|
|
||||||
|
if (success) {
|
||||||
for (item in items) {
|
for (item in items) {
|
||||||
markAsReadLocally(item)
|
markAsReadLocally(item)
|
||||||
}
|
}
|
||||||
@ -271,46 +230,45 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
tags: String,
|
tags: String,
|
||||||
filter: String
|
filter: String
|
||||||
): Boolean {
|
): Boolean {
|
||||||
var response = false
|
// TODO: Check connectivity
|
||||||
if (isNetworkAvailable()) {
|
val response = api.createSourceForVersion(
|
||||||
response = api.createSourceForVersion(
|
title,
|
||||||
title,
|
url,
|
||||||
url,
|
spout,
|
||||||
spout,
|
tags,
|
||||||
tags,
|
filter,
|
||||||
filter,
|
apiMajorVersion
|
||||||
apiMajorVersion
|
)
|
||||||
)?.isSuccess == true
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
return response != null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteSource(id: Int): Boolean {
|
suspend fun deleteSource(id: Int): Boolean {
|
||||||
// TODO: Store in DB
|
// TODO: Check connectivity, store in DB
|
||||||
var success = false
|
var success = false
|
||||||
if (isNetworkAvailable()) {
|
val response = api.deleteSource(id)
|
||||||
val response = api.deleteSource(id)
|
if (response != null) {
|
||||||
if (response != null) {
|
success = response.isSuccess
|
||||||
success = response.isSuccess
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateRemote(): Boolean =
|
suspend fun updateRemote(): Boolean {
|
||||||
isNetworkAvailable() && api.update()?.isSuccess == true
|
// TODO: Handle connectivity issues
|
||||||
|
val response = api.update()
|
||||||
|
return response?.isSuccess ?: false
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun login(): Boolean {
|
suspend fun login(): Boolean {
|
||||||
var result = false
|
var result = false
|
||||||
if (isNetworkAvailable()) {
|
try {
|
||||||
try {
|
val response = api.login()
|
||||||
val response = api.login()
|
if (response != null && response.isSuccess) {
|
||||||
result = response?.isSuccess == true
|
result = true
|
||||||
} catch (cause: Throwable) {
|
|
||||||
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.updateRemote")
|
|
||||||
}
|
}
|
||||||
|
} catch (cause: Throwable) {
|
||||||
|
Napier.e(cause.stackTraceToString(),tag = "RepositoryImpl.updateRemote")
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -329,18 +287,15 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateApiVersion() {
|
private suspend fun updateApiVersion() {
|
||||||
apiMajorVersion = settings.getInt("apiVersionMajor", 0)
|
// TODO: Handle connectivity issues
|
||||||
|
val fetchedVersion = api.version()
|
||||||
if (isNetworkAvailable()) {
|
if (fetchedVersion != null) {
|
||||||
val fetchedVersion = api.version()
|
apiMajorVersion = fetchedVersion.getApiMajorVersion()
|
||||||
if (fetchedVersion != null) {
|
settings.putInt("apiVersionMajor", apiMajorVersion)
|
||||||
apiMajorVersion = fetchedVersion.getApiMajorVersion()
|
} else {
|
||||||
settings.putInt("apiVersionMajor", apiMajorVersion)
|
apiMajorVersion = settings.getInt("apiVersionMajor", 0)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride
|
|
||||||
|
|
||||||
// TODO: Handle offline actions
|
// TODO: Handle offline actions
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user