Compare commits

..

67 Commits

Author SHA1 Message Date
Amine Louveau
8b0bbe71c9 Merge pull request 'Allow offline filtering' (#75) from davidoskky/ReaderForSelfoss-multiplatform:offline_filters into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/75
2022-10-14 07:18:42 +00:00
8bfe14c019 Actually filter database items
All checks were successful
continuous-integration/drone/pr Build is passing
2022-10-14 00:10:35 +02:00
208babbce3 Correct tests 2022-10-14 00:03:20 +02:00
02098a7aa9 Rearrange filtering steps
All checks were successful
continuous-integration/drone/pr Build is passing
2022-10-11 00:52:12 +02:00
d0a982f385 Add tests for offline filtering
All checks were successful
continuous-integration/drone/pr Build is passing
2022-10-08 17:15:41 +02:00
1d1c121aab Filter items from database according to tag and source 2022-10-08 17:15:22 +02:00
fe12819163 Correct database source title 2022-10-08 17:14:12 +02:00
Amine Louveau
023a30c008 Merge pull request 'Simplify sources and tags handling' (#70) from davidoskky/ReaderForSelfoss-multiplatform:drawer_data into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/70
2022-10-04 18:52:39 +00:00
Amine Louveau
a2862a2587 Merge pull request 'Correct mechanism of mark and unmark snackbars' (#74) from davidoskky/ReaderForSelfoss-multiplatform:snackbar-mark into master
Some checks are pending
continuous-integration/drone/push Build is running
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/74
2022-10-04 18:43:44 +00:00
Amine Louveau
054e936657 Merge pull request 'Correct boolean serialization' (#73) from davidoskky/ReaderForSelfoss-multiplatform:swiping into master
Some checks are pending
continuous-integration/drone/push Build is running
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/73
2022-10-04 18:40:14 +00:00
1d2e5069b8 Avoid double snackbar generation
All checks were successful
continuous-integration/drone/pr Build is passing
2022-10-04 16:47:13 +02:00
a147646743 Correct mechanism of mark and unmark snackbars
Some checks are pending
continuous-integration/drone/pr Build is running
2022-10-04 16:43:21 +02:00
32e7fc0038 Correct boolean serialization
All checks were successful
continuous-integration/drone/pr Build is passing
2022-10-04 15:01:22 +02:00
c15bf44032 Adjust repository tests
All checks were successful
continuous-integration/drone/pr Build is passing
2022-10-02 01:01:39 +02:00
0bcd55bd4e Add translated strings
Some checks failed
continuous-integration/drone/pr Build is failing
2022-10-01 22:51:09 +02:00
ebef0b3511 Start monitoring connectivity status when the repository is initiated.
Some checks are pending
continuous-integration/drone/pr Build is running
2022-10-01 22:43:48 +02:00
713ceb05bf Remove unnecessary data class
Some checks failed
continuous-integration/drone/pr Build is failing
2022-09-30 15:07:17 +02:00
dc8381b661 Add missing string 2022-09-30 15:00:25 +02:00
b5b820c64b Remove database access from the Home 2022-09-30 15:00:01 +02:00
f7055626d9 Start monitoring the connectivity before loading the Repository 2022-09-30 14:56:10 +02:00
Amine Louveau
6ec3e96909 Merge pull request 'Repository Unit Tests' (#50) from davidoskky/ReaderForSelfoss-multiplatform:repository_tests into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/50
2022-09-30 11:31:55 +00:00
22da30eaa8 Remove unnecessary call to api
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-30 13:17:40 +02:00
79fd115f5e Only return new cached items 2022-09-30 13:16:42 +02:00
8dc3d319cd Cleanup
Some checks failed
continuous-integration/drone/pr Build is failing
2022-09-30 11:59:08 +02:00
27bb056397 Cleanup
Some checks are pending
continuous-integration/drone/pr Build is running
2022-09-30 11:49:31 +02:00
f9ba13dc32 Always cache images in background
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-30 11:23:43 +02:00
6f60ef4346 Remove unnecessary return value
Some checks failed
continuous-integration/drone/pr Build is failing
2022-09-30 09:11:55 +02:00
28b950f467 Merge branch 'master' into repository_tests
Some checks are pending
continuous-integration/drone/pr Build is running
2022-09-30 07:04:09 +00:00
a9c7ec3dc1 Cache items in background without filtering
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-30 01:19:28 +02:00
920d4ac1ef Correctly implement disabling sources update 2022-09-29 19:37:33 +02:00
0e96d313ec Add tags parameters explicitly 2022-09-29 19:09:09 +02:00
7211fdb1a3 Fix update remote tests 2022-09-29 18:58:00 +02:00
aminecmi
381d6acc82 Analyzing should be with the rest.
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-29 15:34:12 +02:00
d311c2cdeb Fix sources tests 2022-09-28 18:45:21 +02:00
219cae5d74 Fix tags tests 2022-09-28 18:22:06 +02:00
2968aee309 Fix badges tests 2022-09-28 09:43:28 +02:00
6cb4b35c93 Introduce useful assertions in repository instantiation tests 2022-09-28 09:14:47 +02:00
15ec0f2d26 Merge branch 'master' into repository_tests
Some checks failed
continuous-integration/drone/pr Build is failing
2022-09-28 06:57:02 +00:00
4781e30da2 Remove unnecessary safe calls
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-27 23:44:42 +02:00
c8759cc035 Fix tags tests
Some checks are pending
continuous-integration/drone/pr Build is running
2022-09-27 23:37:30 +02:00
cb4f2f02ef Fix repository.tags() returning null 2022-09-27 23:26:44 +02:00
7517626ab7 Include database return definition within test function 2022-09-27 23:25:47 +02:00
41c951b659 Add test cases for repository instantiation cases
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-27 23:16:30 +02:00
aminecmi
ad279c6683 Translation.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2022-09-27 16:57:13 +02:00
aminecmi
5f0817ddb7 Formating issue.
Some checks are pending
continuous-integration/drone/push Build is running
2022-09-27 16:54:57 +02:00
aminecmi
7124cbcacd Fixed issue with drone failing silently.
Analyzation is now detached.
2022-09-27 16:53:18 +02:00
aminecmi
2a710a1a08 Translations.
Some checks are pending
continuous-integration/drone/push Build is running
2022-09-27 16:50:06 +02:00
Amine Louveau
82ec2445a1 Merge pull request 'chore/theme-cleaning' (#69) from chore/theme-cleaning into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/69
2022-09-27 14:37:23 +00:00
aminecmi
cabb6d494d Cleaning.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-09-27 15:09:33 +02:00
aminecmi
5c12481813 Article viewer theming.
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-27 12:36:01 +02:00
e2afff0b8e Add comment
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-26 23:19:31 +02:00
a382fc89ea Test item caching
Some checks are pending
continuous-integration/drone/pr Build is running
2022-09-26 23:11:26 +02:00
3f0a3903ae Test refresh login information
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-26 22:50:55 +02:00
f46f98cef0 Test login
Some checks are pending
continuous-integration/drone/pr Build is running
2022-09-26 22:46:37 +02:00
bf6f1a917e Test update remote
Some checks are pending
continuous-integration/drone/pr Build is running
2022-09-26 22:42:24 +02:00
71c0a4d340 Test delete source
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-26 22:26:01 +02:00
63c550ead3 Test create source
Some checks are pending
continuous-integration/drone/pr Build is running
2022-09-26 22:21:48 +02:00
366b2e10f1 Adjust tests to changes in the data models
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-25 22:02:25 +02:00
d2436bb976 Merge branch 'master' into repository_tests
# Conflicts:
#	.drone.yml
2022-09-25 20:24:46 +02:00
ef994460c1 get sources tests
Some checks failed
continuous-integration/drone/pr Build is failing
2022-09-18 18:14:14 +02:00
758708e18d Tags tests
Some checks failed
continuous-integration/drone/pr Build is failing
2022-09-17 22:04:24 +02:00
c0381144d1 Add CI test step
Some checks failed
continuous-integration/drone/pr Build is failing
2022-09-17 21:29:37 +02:00
cda3ba6cb4 Test badge fetching
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-16 12:04:05 +02:00
a4636cc0c8 Add item fetching tests
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-15 14:07:50 +02:00
60c24fc75a Check that the api is being used rather than the db
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-10 09:37:14 +02:00
5853a19937 Normal items fetch test
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-10 09:08:26 +02:00
99f2c04bf6 Initial testing setup
All checks were successful
continuous-integration/drone/pr Build is passing
2022-09-09 13:43:53 +02:00
38 changed files with 1314 additions and 131 deletions

View File

@@ -5,7 +5,6 @@ name: test
steps: steps:
- name: AnylyseBuildTest - name: AnylyseBuildTest
image: mingc/android-build-box:latest image: mingc/android-build-box:latest
failure: ignore
commands: commands:
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Analysing..." - echo "Analysing..."
@@ -16,6 +15,7 @@ steps:
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Testing..." - echo "Testing..."
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- ./gradlew test -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
environment: environment:
SONAR_HOST_URL: SONAR_HOST_URL:
from_secret: sonarScannerHostUrl from_secret: sonarScannerHostUrl

View File

@@ -15,9 +15,6 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.doOnNextLayout import androidx.core.view.doOnNextLayout
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
@@ -92,15 +89,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private val settingsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { private val settingsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
appSettingsService.refreshUserSettings() appSettingsService.refreshUserSettings()
AppCompatDelegate.setDefaultNightMode(if (appSettingsService.isDarkThemeEnabled()) MODE_NIGHT_YES else MODE_NIGHT_NO)
} }
override val di by closestDI() override val di by closestDI()
private val repository : Repository by instance() private val repository : Repository by instance()
private val appSettingsService : AppSettingsService by instance() private val appSettingsService : AppSettingsService by instance()
data class DrawerData(val tags: List<SelfossModel.Tag>?, val sources: List<SelfossModel.Source>?)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater) binding = ActivityHomeBinding.inflate(layoutInflater)
@@ -353,27 +347,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
) )
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val drawerData = DrawerData(repository.getDBTags().map { it.toView() }, val tags = repository.getTags()
repository.getDBSources().map { it.toView() }) val sources = repository.getSources()
runOnUiThread { runOnUiThread {
// Only refresh if there is no data in the DB, or if the `UpdateSources` setting is enabled handleDrawerData(tags, sources)
if (drawerData.sources?.isEmpty() == true || appSettingsService.isUpdateSourcesEnabled()) {
drawerApiCalls(drawerData)
} else {
handleDrawerData(drawerData, loadedFromCache = true)
}
} }
} }
} }
private fun drawerApiCalls(drawerData: DrawerData) { private fun handleDrawerData(tags: List<SelfossModel.Tag>, sources: List<SelfossModel.Source>) {
CoroutineScope(Dispatchers.Main).launch {
val apiDrawerData = DrawerData(repository.getTags(), repository.getSources())
handleDrawerData(if (drawerData != apiDrawerData) apiDrawerData else drawerData)
}
}
private fun handleDrawerData(drawerData: DrawerData, loadedFromCache: Boolean = false) {
binding.mainDrawer.itemAdapter.clear() binding.mainDrawer.itemAdapter.clear()
// Filters title with clear action // Filters title with clear action
@@ -387,24 +369,24 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
} }
// Hidden tags // Hidden tags
if (drawerData.tags != null && drawerData.tags.isNotEmpty() && appSettingsService.getHiddenTags().isNotEmpty()) { if (tags.isNotEmpty() && appSettingsService.getHiddenTags().isNotEmpty()) {
secondaryItem( secondaryItem(
withDivider = true, withDivider = true,
R.string.drawer_item_hidden_tags, R.string.drawer_item_hidden_tags,
DRAWER_ID_HIDDEN_TAGS DRAWER_ID_HIDDEN_TAGS
) )
handleHiddenTags(drawerData.tags) handleHiddenTags(tags)
} }
// Tags // Tags
secondaryItem(withDivider = true, R.string.drawer_item_tags, DRAWER_ID_TAGS) secondaryItem(withDivider = true, R.string.drawer_item_tags, DRAWER_ID_TAGS)
if (drawerData.tags == null && !loadedFromCache) { if (tags.isEmpty()) {
binding.mainDrawer.itemAdapter.add( binding.mainDrawer.itemAdapter.add(
SecondaryDrawerItem() SecondaryDrawerItem()
.apply { nameRes = R.string.drawer_error_loading_tags; isSelectable = false } .apply { nameRes = R.string.drawer_error_loading_tags; isSelectable = false }
) )
} else { } else {
handleTags(drawerData.tags!!) handleTags(tags)
} }
// Sources // Sources
@@ -412,15 +394,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
startActivity(Intent(v!!.context, SourcesActivity::class.java)) startActivity(Intent(v!!.context, SourcesActivity::class.java))
false false
} }
if (drawerData.sources == null && !loadedFromCache) { if (sources.isEmpty()) {
binding.mainDrawer.itemAdapter.add( binding.mainDrawer.itemAdapter.add(
SecondaryDrawerItem().apply { SecondaryDrawerItem().apply {
nameRes = R.string.drawer_error_loading_tags nameRes = R.string.drawer_error_loading_sources
isSelectable = false isSelectable = false
} }
) )
} else { } else {
handleSources(drawerData.sources!!) handleSources(sources)
} }
// About action // About action

View File

@@ -38,7 +38,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
AppCompatDelegate.setDefaultNightMode(if (appSettingsService.isDarkThemeEnabled()) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
binding = ActivityLoginBinding.inflate(layoutInflater) binding = ActivityLoginBinding.inflate(layoutInflater)
val view = binding.root val view = binding.root
@@ -163,7 +163,6 @@ class LoginActivity : AppCompatActivity(), DIAware {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val result = repository.login() val result = repository.login()
if (result) { if (result) {
repository.updateApiVersion()
goToMain() goToMain()
} else { } else {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {

View File

@@ -75,7 +75,6 @@ class MyApp : MultiDexApplication(), DIAware {
).show() ).show()
} }
} }
} }
private fun handleNotificationChannels() { private fun handleNotificationChannels() {

View File

@@ -28,7 +28,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
updateItems(this.items) updateItems(this.items)
} }
private fun unmarkSnackbar(position: Int) { private fun unmarkSnackbar(item: SelfossModel.Item, position: Int) {
val s = Snackbar val s = Snackbar
.make( .make(
app.findViewById(R.id.coordLayout), app.findViewById(R.id.coordLayout),
@@ -37,7 +37,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
) )
.setAction(R.string.undo_string) { .setAction(R.string.undo_string) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
unreadItemAtIndex(position, false) unreadItemAtIndex(item, position, false)
} }
} }
@@ -47,7 +47,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
s.show() s.show()
} }
private fun markSnackbar(position: Int) { private fun markSnackbar(item: SelfossModel.Item, position: Int) {
val s = Snackbar val s = Snackbar
.make( .make(
app.findViewById(R.id.coordLayout), app.findViewById(R.id.coordLayout),
@@ -55,7 +55,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
Snackbar.LENGTH_LONG Snackbar.LENGTH_LONG
) )
.setAction(R.string.undo_string) { .setAction(R.string.undo_string) {
readItemAtIndex(position) readItemAtIndex(item, position, false)
} }
val view = s.view val view = s.view
@@ -66,37 +66,36 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
fun handleItemAtIndex(position: Int) { fun handleItemAtIndex(position: Int) {
if (items[position].unread) { if (items[position].unread) {
readItemAtIndex(position) readItemAtIndex(items[position], position)
} else { } else {
unreadItemAtIndex(position) unreadItemAtIndex(items[position], position)
} }
} }
private fun readItemAtIndex(position: Int, showSnackbar: Boolean = true) { private fun readItemAtIndex(item: SelfossModel.Item, position: Int, showSnackbar: Boolean = true) {
val i = items[position]
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(i) repository.markAsRead(item)
} }
if (repository.displayedItems == ItemType.UNREAD) { if (repository.displayedItems == ItemType.UNREAD) {
items.remove(i) items.remove(item)
notifyItemRemoved(position) notifyItemRemoved(position)
updateItems(items) updateItems(items)
} else { } else {
notifyItemChanged(position) notifyItemChanged(position)
} }
if (showSnackbar) { if (showSnackbar) {
unmarkSnackbar(position) unmarkSnackbar(item, position)
} }
} }
private fun unreadItemAtIndex(position: Int, showSnackbar: Boolean = true) { private fun unreadItemAtIndex(item: SelfossModel.Item, position: Int, showSnackbar: Boolean = true) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(items[position]) repository.unmarkAsRead(item)
} }
notifyItemChanged(position) notifyItemChanged(position)
if (showSnackbar) { if (showSnackbar) {
markSnackbar(position) markSnackbar(item, position)
} }
} }

View File

@@ -52,11 +52,13 @@ override fun doWork(): Result {
repository.handleDBActions() repository.handleDBActions()
val apiItems = repository.tryToCacheItemsAndGetNewOnes()
if (appSettingsService.isNotifyNewItemsEnabled()) { if (appSettingsService.isNotifyNewItemsEnabled()) {
launch { launch {
handleNewItemsNotification(repository.tryToCacheItemsAndGetNewOnes(), notificationManager) handleNewItemsNotification(apiItems, notificationManager)
} }
} }
apiItems.map { it.preloadImages(context) }
} }
} }
return Result.success() return Result.success()
@@ -66,6 +68,7 @@ override fun doWork(): Result {
newItems: List<SelfossModel.Item>?, newItems: List<SelfossModel.Item>?,
notificationManager: NotificationManager notificationManager: NotificationManager
) { ) {
// TODO: Check if this coroutine is actually required
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val apiItems = newItems.orEmpty() val apiItems = newItems.orEmpty()
@@ -102,7 +105,6 @@ override fun doWork(): Result {
notificationManager.notify(2, newItemsNotification.build()) notificationManager.notify(2, newItemsNotification.build())
} }
} }
apiItems.map { it.preloadImages(context) }
Timer("", false).schedule(4000) { Timer("", false).schedule(4000) {
notificationManager.cancel(1) notificationManager.cancel(1)
} }

View File

@@ -8,6 +8,7 @@ import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.TypedValue
import android.view.* import android.view.*
import android.webkit.WebResourceResponse import android.webkit.WebResourceResponse
import android.webkit.WebSettings import android.webkit.WebSettings
@@ -56,6 +57,7 @@ import java.net.URL
import java.util.* import java.util.*
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
class ArticleFragment : Fragment(), DIAware { class ArticleFragment : Fragment(), DIAware {
private var fontSize: Int = 16 private var fontSize: Int = 16
private lateinit var item: SelfossModel.Item private lateinit var item: SelfossModel.Item
@@ -333,7 +335,6 @@ class ArticleFragment : Fragment(), DIAware {
} }
private fun htmlToWebview() { private fun htmlToWebview() {
val stringColor = String.format("#%06X", 0xFFFFFF and resources.getColor(R.color.colorAccent))
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily) val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs) val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
@@ -342,12 +343,11 @@ class ArticleFragment : Fragment(), DIAware {
binding.webcontent.settings.standardFontFamily = a.getString(0) binding.webcontent.settings.standardFontFamily = a.getString(0)
binding.webcontent.visibility = View.VISIBLE binding.webcontent.visibility = View.VISIBLE
// TODO: Set the color strings programmatically val colorOnSurface = TypedValue()
val (stringTextColor, stringBackgroundColor) = if (appSettingsService.isDarkThemeEnabled()) { requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
Pair("#FFFFFF", "#303030")
} else { val colorSurface = TypedValue()
Pair("#212121", "#FAFAFA") requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
}
binding.webcontent.settings.useWideViewPort = true binding.webcontent.settings.useWideViewPort = true
binding.webcontent.settings.loadWithOverviewMode = true binding.webcontent.settings.loadWithOverviewMode = true
@@ -436,10 +436,10 @@ class ArticleFragment : Fragment(), DIAware {
| max-width: 100%; | max-width: 100%;
| } | }
| a { | a {
| color: $stringColor !important; | color: ${String.format("#%06X", 0xFFFFFF and resources.getColor(R.color.colorAccent))} !important;
| } | }
| *:not(a) { | *:not(a) {
| color: $stringTextColor; | color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
| } | }
| * { | * {
| font-size: ${fontSize}px; | font-size: ${fontSize}px;
@@ -447,11 +447,11 @@ class ArticleFragment : Fragment(), DIAware {
| word-break: break-word; | word-break: break-word;
| overflow:hidden; | overflow:hidden;
| line-height: 1.5em; | line-height: 1.5em;
| background-color: $stringBackgroundColor; | background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
| } | }
| body, html { | body, html {
| background-color: $stringBackgroundColor !important; | background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)} !important;
| border-color: $stringBackgroundColor !important; | border-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)} !important;
| padding: 0 !important; | padding: 0 !important;
| margin: 0 !important; | margin: 0 !important;
| } | }
@@ -461,7 +461,7 @@ class ArticleFragment : Fragment(), DIAware {
| pre, code { | pre, code {
| white-space: pre-wrap; | white-space: pre-wrap;
| width:100%; | width:100%;
| background-color: $stringBackgroundColor; | background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
| } | }
| </style> | </style>
| $fontLinkAndStyle | $fontLinkAndStyle

View File

@@ -9,6 +9,7 @@ import android.text.InputType
import android.text.TextWatcher import android.text.TextWatcher
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.Preference import androidx.preference.Preference
@@ -155,7 +156,11 @@ class SettingsActivity : AppCompatActivity(),
class ThemePreferenceFragment : PreferenceFragmentCompat() { class ThemePreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_theme, rootKey) setPreferencesFromResource(R.xml.pref_theme, rootKey)
setHasOptionsMenu(true)
preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
true
}
} }
} }

View File

@@ -16,8 +16,7 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
card_view:cardElevation="2dp" card_view:cardElevation="2dp"
card_view:cardUseCompatPadding="true" card_view:cardUseCompatPadding="true"
card_view:layout_constraintBottom_toBottomOf="parent" card_view:layout_constraintBottom_toBottomOf="parent">
app:cardBackgroundColor="?cardBackgroundColor">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -66,7 +66,7 @@
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:paddingBottom="48dp" android:paddingBottom="48dp"
android:background="?android:colorBackground" android:background="?attr/webviewBackground"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -8,7 +8,7 @@
<string name="error_field_required">"Champ requis"</string> <string name="error_field_required">"Champ requis"</string>
<string name="prompt_url">"Url Selfoss"</string> <string name="prompt_url">"Url Selfoss"</string>
<string name="withLoginSwitch">"Avec login ?"</string> <string name="withLoginSwitch">"Avec login ?"</string>
<string name="login_url_problem">"Petit souci. Il manque peut être un / à la fin ?"</string> <string name="login_url_problem">"Petit souci. Il manque peut-être un / à la fin ?"</string>
<string name="prompt_login">"Utilisateur"</string> <string name="prompt_login">"Utilisateur"</string>
<string name="label_share">"Partager"</string> <string name="label_share">"Partager"</string>
<string name="readAll">"Tout lire"</string> <string name="readAll">"Tout lire"</string>
@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">La barre sera affichée</string> <string name="reader_static_bar_on">La barre sera affichée</string>
<string name="reader_static_bar_off">La barre sera affichée grâce au bouton</string> <string name="reader_static_bar_off">La barre sera affichée grâce au bouton</string>
<string name="remove_source">Supprimer la source</string> <string name="remove_source">Supprimer la source</string>
<string name="pref_theme_title">Thème Clair/Sombre</string>
<string name="mode_dark">Thème sombre</string>
<string name="mode_system">Utiliser les paramètres système</string>
<string name="mode_light">Thème clair</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">A barra inferior mostrarase sempre</string> <string name="reader_static_bar_on">A barra inferior mostrarase sempre</string>
<string name="reader_static_bar_off">A barra inferior pode mostrarse a través do botón flotante</string> <string name="reader_static_bar_off">A barra inferior pode mostrarse a través do botón flotante</string>
<string name="remove_source">Eliminar fonte</string> <string name="remove_source">Eliminar fonte</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -4,10 +4,10 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
<item name="colorAccentDark">@color/colorAccentDark</item> <item name="colorAccentDark">@color/colorAccentDark</item>
<item name="cardBackgroundColor">@color/white</item>
<item name="preferenceTheme">@style/PreferenceStyle</item> <item name="preferenceTheme">@style/PreferenceStyle</item>
<item name="android:statusBarColor">@color/dark</item> <item name="android:statusBarColor">@color/dark</item>
<item name="bottomBarBackground">@color/dark</item> <item name="bottomBarBackground">@color/dark</item>
<item name="toolbarPopupTheme">@style/ThemeOverlay.AppCompat.Dark</item> <item name="toolbarPopupTheme">@style/ThemeOverlay.AppCompat.Dark</item>
<item name="webviewBackground">@color/dark</item>
</style> </style>
</resources> </resources>

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -47,7 +47,7 @@
<string name="switch_unread_count_title">"显示未读数"</string> <string name="switch_unread_count_title">"显示未读数"</string>
<string name="display_all_counts_title">"显示收藏和已读的计数"</string> <string name="display_all_counts_title">"显示收藏和已读的计数"</string>
<string name="text_wrong_url">"您似乎试图使用无效的 URL。确保它是正确的如果问题仍然存在请与我联系 (通过商店的联系链接)。请注意,该应用程序需要您使用 Selfoss。没有它您无法访问 RSS 源。"</string> <string name="text_wrong_url">"您似乎试图使用无效的 URL。确保它是正确的如果问题仍然存在请与我联系 (通过商店的联系链接)。请注意,该应用程序需要您使用 Selfoss。没有它您无法访问 RSS 源。"</string>
<string name="pref_article_viewer_title">"打开应用程序中的链接"</string> <string name="pref_article_viewer_title">"在应用内打开链接"</string>
<string name="pref_article_viewer_on">"文章将在应用程序内打开"</string> <string name="pref_article_viewer_on">"文章将在应用程序内打开"</string>
<string name="pref_article_viewer_off">"文章将使用默认浏览器打开"</string> <string name="pref_article_viewer_off">"文章将使用默认浏览器打开"</string>
<string name="pref_general_category_links">"链接处理"</string> <string name="pref_general_category_links">"链接处理"</string>
@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">底部栏将始终显示</string> <string name="reader_static_bar_on">底部栏将始终显示</string>
<string name="reader_static_bar_off">底部栏可以通过浮动按钮显示</string> <string name="reader_static_bar_off">底部栏可以通过浮动按钮显示</string>
<string name="remove_source">删除源</string> <string name="remove_source">删除源</string>
<string name="pref_theme_title">浅色/深色模式</string>
<string name="mode_dark">深色模式</string>
<string name="mode_system">遵循系统设置</string>
<string name="mode_light">浅色模式</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -128,4 +128,9 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
</resources> </resources>

View File

@@ -4,5 +4,6 @@
<attr name="colorAccentDark" format="reference|color" /> <attr name="colorAccentDark" format="reference|color" />
<attr name="bottomBarBackground" format="reference|color" /> <attr name="bottomBarBackground" format="reference|color" />
<attr name="toolbarPopupTheme" format="reference|color" /> <attr name="toolbarPopupTheme" format="reference|color" />
<attr name="webviewBackground" format="reference|color" />
</declare-styleable> </declare-styleable>
</resources> </resources>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="ModeTitles">
<item>@string/mode_light</item>
<item>@string/mode_dark</item>
<item>@string/mode_system</item>
</string-array>
<string-array name="ModeValues">
<item>1</item> <!--MODE_NIGHT_NO-->
<item>2</item> <!--MODE_NIGHT_YES-->
<item>0</item> <!--MODE_NIGHT_AUTO_TIME-->
</string-array>
<string-array name="Voice">
<item>Male</item>
<item>Female</item>
</string-array>
<string-array name="VoiceAlias">
<item>"usenglishmale"</item>
<item>"usenglishfemale"</item>
<item>"ukenglishmale"</item>
<item>"ukenglishfemale"</item>
<item>"eurfrenchmale"</item>
<item>"eurfrenchfemale"</item>
<item>"eurspanishmale"</item>
<item>"eurspanishfemale"</item>
<item>"euritalianmale"</item>
<item>"euritalianfemale"</item>
</string-array>
</resources>

View File

@@ -63,6 +63,7 @@
<string name="card_height_off">Card height will be fixed</string> <string name="card_height_off">Card height will be fixed</string>
<string name="source_code">Source code</string> <string name="source_code">Source code</string>
<string name="drawer_error_loading_tags">Error loading tags…</string> <string name="drawer_error_loading_tags">Error loading tags…</string>
<string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="drawer_item_filters">Filters</string> <string name="drawer_item_filters">Filters</string>
<string name="drawer_action_clear">clear</string> <string name="drawer_action_clear">clear</string>
<string name="drawer_item_tags">Tags</string> <string name="drawer_item_tags">Tags</string>
@@ -109,7 +110,7 @@
<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>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading ...</string> <string name="loading_notification_title">Loading </string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@@ -130,4 +131,8 @@
<string name="reader_static_bar_on">The bottom bar will always be displayed</string> <string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string> <string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string> <string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string>
</resources> </resources>

View File

@@ -9,11 +9,11 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
<item name="colorAccentDark">@color/colorAccentDark</item> <item name="colorAccentDark">@color/colorAccentDark</item>
<item name="cardBackgroundColor">@color/white</item>
<item name="preferenceTheme">@style/PreferenceStyle</item> <item name="preferenceTheme">@style/PreferenceStyle</item>
<item name="android:statusBarColor">?attr/colorPrimary</item> <item name="android:statusBarColor">?attr/colorPrimary</item>
<item name="bottomBarBackground">@color/white</item> <item name="bottomBarBackground">@color/white</item>
<item name="toolbarPopupTheme">@style/ThemeOverlay.AppCompat.Light</item> <item name="toolbarPopupTheme">@style/ThemeOverlay.AppCompat.Light</item>
<item name="webviewBackground">@color/white</item>
</style> </style>
<!-- Preference Theme --> <!-- Preference Theme -->

View File

@@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- TODO translate this file -->
<SwitchPreference <ListPreference
android:defaultValue="false" android:defaultValue="0"
android:key="dark_theme" android:entries="@array/ModeTitles"
android:entryValues="@array/ModeValues"
android:key="currentMode"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
android:title="Dark theme" /> android:title="@string/pref_theme_title"
app:useSimpleSummaryProvider="false" />
</PreferenceScreen> </PreferenceScreen>

View File

@@ -56,6 +56,8 @@ kotlin {
dependencies { dependencies {
implementation(kotlin("test-common")) implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common")) implementation(kotlin("test-annotations-common"))
implementation("io.mockk:mockk:1.12.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
} }
} }
val androidMain by getting { val androidMain by getting {

View File

@@ -138,7 +138,11 @@ class SelfossModel {
object BooleanSerializer : KSerializer<Boolean> { object BooleanSerializer : KSerializer<Boolean> {
override fun deserialize(decoder: Decoder): Boolean { override fun deserialize(decoder: Decoder): Boolean {
val json = ((decoder as JsonDecoder).decodeJsonElement()).jsonPrimitive val json = ((decoder as JsonDecoder).decodeJsonElement()).jsonPrimitive
return json.booleanOrNull ?: json.int == 1 return if (json.booleanOrNull != null) {
json.boolean
} else {
json.int == 1
}
} }
override val descriptor: SerialDescriptor override val descriptor: SerialDescriptor

View File

@@ -11,6 +11,7 @@ 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
import kotlinx.coroutines.runBlocking
class Repository(private val api: SelfossApi, private val appSettingsService: AppSettingsService, connectivityStatus: ConnectivityStatus, private val db: ReaderForSelfossDB) { class Repository(private val api: SelfossApi, private val appSettingsService: AppSettingsService, connectivityStatus: ConnectivityStatus, private val db: ReaderForSelfossDB) {
@@ -36,9 +37,13 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
var badgeStarred = 0 var badgeStarred = 0
set(value) {field = if (value < 0) { 0 } else { value } } set(value) {field = if (value < 0) { 0 } else { value } }
private var fetchedSources = false
private var fetchedTags = false
init { init {
// TODO: Dispatchers.IO not available in KMM, an alternative solution should be found // TODO: Dispatchers.IO not available in KMM, an alternative solution should be found
CoroutineScope(Dispatchers.Main).launch { connectivityStatus.start()
runBlocking {
updateApiVersion() updateApiVersion()
dateUtils = DateUtils(appSettingsService) dateUtils = DateUtils(appSettingsService)
reloadBadges() reloadBadges()
@@ -61,12 +66,19 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
} else { } else {
if (appSettingsService.isItemCachingEnabled()) { if (appSettingsService.isItemCachingEnabled()) {
fromDB = true fromDB = true
var dbItems = getDBItems().filter {
displayedItems == ItemType.ALL ||
(it.unread && displayedItems == ItemType.UNREAD) ||
(it.starred && displayedItems == ItemType.STARRED)
}
if (tagFilter != null) {
dbItems = dbItems.filter { it.tags.split(',').contains(tagFilter!!.tag) }
}
if (sourceFilter != null) {
dbItems = dbItems.filter { it.sourcetitle == sourceFilter!!.title }
}
fetchedItems = SelfossModel.StatusAndData.succes( fetchedItems = SelfossModel.StatusAndData.succes(
getDBItems().filter { dbItems.map { it.toView() }
displayedItems == ItemType.ALL ||
(it.unread && displayedItems == ItemType.UNREAD) ||
(it.starred && displayedItems == ItemType.STARRED)
}.map { it.toView() }
) )
} }
} }
@@ -105,9 +117,9 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
val items = api.getItems( val items = api.getItems(
itemType.type, itemType.type,
0, 0,
tagFilter?.tag, null,
sourceFilter?.id?.toLong(), null,
searchFilter, null,
null, null,
200 200
) )
@@ -131,32 +143,40 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
badgeStarred = response.data.starred badgeStarred = response.data.starred
success = true success = true
} }
} else { } else if (appSettingsService.isItemCachingEnabled()) {
// TODO: do this differently, because it's not efficient // TODO: do this differently, because it's not efficient
val dbItems = getDBItems() val dbItems = getDBItems()
badgeUnread = dbItems.filter { item -> item.unread }.size badgeUnread = dbItems.filter { item -> item.unread }.size
badgeStarred = dbItems.filter { item -> item.starred }.size badgeStarred = dbItems.filter { item -> item.starred }.size
badgeAll = items.size badgeAll = dbItems.size
success = true
} }
return success return success
} }
suspend fun getTags(): List<SelfossModel.Tag>? { suspend fun getTags(): List<SelfossModel.Tag> {
return if (isNetworkAvailable()) { val isDatabaseEnabled = appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
return if (isNetworkAvailable() && !fetchedTags) {
val apiTags = api.tags() val apiTags = api.tags()
if (apiTags.success && apiTags.data != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) { if (apiTags.success && apiTags.data != null && isDatabaseEnabled) {
resetDBTagsWithData(apiTags.data) resetDBTagsWithData(apiTags.data)
if (!appSettingsService.isUpdateSourcesEnabled()) {
fetchedTags = true
}
} }
apiTags.data apiTags.data ?: emptyList()
} else { } else if (isDatabaseEnabled) {
getDBTags().map { it.toView() } getDBTags().map { it.toView() }
} else {
emptyList()
} }
} }
suspend fun getSpouts(): Map<String, SelfossModel.Spout>? { // TODO: Add tests
suspend fun getSpouts(): Map<String, SelfossModel.Spout> {
return if (isNetworkAvailable()) { return if (isNetworkAvailable()) {
val spouts = api.spouts() val spouts = api.spouts()
return if (spouts.success && spouts.data != null) { if (spouts.success && spouts.data != null) {
spouts.data spouts.data
} else { } else {
emptyMap() // TODO: do something here emptyMap() // TODO: do something here
@@ -166,18 +186,25 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
} }
} }
suspend fun getSources(): ArrayList<SelfossModel.Source>? { suspend fun getSources(): ArrayList<SelfossModel.Source> {
return if (isNetworkAvailable()) { val isDatabaseEnabled = appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
return if (isNetworkAvailable() && !fetchedSources) {
val apiSources = api.sources() val apiSources = api.sources()
if (apiSources.success && apiSources.data != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) { if (apiSources.success && apiSources.data != null && isDatabaseEnabled) {
resetDBSourcesWithData(apiSources.data) resetDBSourcesWithData(apiSources.data)
if (!appSettingsService.isUpdateSourcesEnabled()) {
fetchedSources = true
}
} }
apiSources.data apiSources.data ?: ArrayList()
} else { } else if (isDatabaseEnabled) {
ArrayList(getDBSources().map { it.toView() }) ArrayList(getDBSources().map { it.toView() })
} else {
ArrayList()
} }
} }
// TODO: Add tests
suspend fun markAsRead(item: SelfossModel.Item): Boolean { suspend fun markAsRead(item: SelfossModel.Item): Boolean {
val success = markAsReadById(item.id) val success = markAsReadById(item.id)
@@ -189,14 +216,14 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun markAsReadById(id: Int): Boolean { private suspend fun markAsReadById(id: Int): Boolean {
return if (isNetworkAvailable()) { return if (isNetworkAvailable()) {
api.markAsRead(id.toString())?.isSuccess api.markAsRead(id.toString()).isSuccess
} else { } else {
insertDBAction(id.toString(), read = true) insertDBAction(id.toString(), read = true)
true true
} }
} }
// TODO: Add tests
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean { suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
val success = unmarkAsReadById(item.id) val success = unmarkAsReadById(item.id)
@@ -208,13 +235,14 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun unmarkAsReadById(id: Int): Boolean { private suspend fun unmarkAsReadById(id: Int): Boolean {
return if (isNetworkAvailable()) { return if (isNetworkAvailable()) {
api.unmarkAsRead(id.toString())?.isSuccess api.unmarkAsRead(id.toString()).isSuccess
} else { } else {
insertDBAction(id.toString(), unread = true) insertDBAction(id.toString(), unread = true)
true true
} }
} }
// TODO: Add tests
suspend fun starr(item: SelfossModel.Item): Boolean { suspend fun starr(item: SelfossModel.Item): Boolean {
val success = starrById(item.id) val success = starrById(item.id)
@@ -226,13 +254,14 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun starrById(id: Int): Boolean { private suspend fun starrById(id: Int): Boolean {
return if (isNetworkAvailable()) { return if (isNetworkAvailable()) {
api.starr(id.toString())?.isSuccess api.starr(id.toString()).isSuccess
} else { } else {
insertDBAction(id.toString(), starred = true) insertDBAction(id.toString(), starred = true)
true true
} }
} }
// TODO: Add tests
suspend fun unstarr(item: SelfossModel.Item): Boolean { suspend fun unstarr(item: SelfossModel.Item): Boolean {
val success = unstarrById(item.id) val success = unstarrById(item.id)
@@ -244,17 +273,18 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun unstarrById(id: Int): Boolean { private suspend fun unstarrById(id: Int): Boolean {
return if (isNetworkAvailable()) { return if (isNetworkAvailable()) {
api.unstarr(id.toString())?.isSuccess api.unstarr(id.toString()).isSuccess
} else { } else {
insertDBAction(id.toString(), starred = true) insertDBAction(id.toString(), starred = true)
true true
} }
} }
// TODO: Add tests
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean { suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
var success = false var success = false
if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess) { if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() }).isSuccess) {
success = true success = true
for (item in items) { for (item in items) {
markAsReadLocally(item) markAsReadLocally(item)
@@ -323,7 +353,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
tags, tags,
filter, filter,
appSettingsService.getApiVersion() appSettingsService.getApiVersion()
)?.isSuccess == true ).isSuccess == true
} }
return response return response
@@ -333,9 +363,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
var success = false var success = false
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
val response = api.deleteSource(id) val response = api.deleteSource(id)
if (response != null) { success = response.isSuccess
success = response.isSuccess
}
} }
return success return success
@@ -343,7 +371,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun updateRemote(): Boolean { suspend fun updateRemote(): Boolean {
return if (isNetworkAvailable()) { return if (isNetworkAvailable()) {
api.update()?.equals("finished") api.update().data.equals("finished")
} else { } else {
false false
} }
@@ -354,7 +382,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
try { try {
val response = api.login() val response = api.login()
result = response?.isSuccess == true result = response.isSuccess == true
if (result) { if (result) {
updateApiVersion() updateApiVersion()
} }
@@ -371,7 +399,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
api.refreshLoginInformation() api.refreshLoginInformation()
} }
suspend fun updateApiVersion() { private suspend fun updateApiVersion() {
val apiMajorVersion = appSettingsService.getApiVersion() val apiMajorVersion = appSettingsService.getApiVersion()
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
@@ -390,9 +418,9 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun deleteDBAction(action: ACTION) = private fun deleteDBAction(action: ACTION) =
db.actionsQueries.deleteAction(action.id) db.actionsQueries.deleteAction(action.id)
fun getDBTags(): List<TAG> = db.tagsQueries.tags().executeAsList() private fun getDBTags(): List<TAG> = db.tagsQueries.tags().executeAsList()
fun getDBSources(): List<SOURCE> = db.sourcesQueries.sources().executeAsList() private fun getDBSources(): List<SOURCE> = db.sourcesQueries.sources().executeAsList()
private fun resetDBTagsWithData(tagEntities: List<SelfossModel.Tag>) { private fun resetDBTagsWithData(tagEntities: List<SelfossModel.Tag>) {
db.tagsQueries.deleteAllTags() db.tagsQueries.deleteAllTags()
@@ -430,8 +458,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun updateDBItem(item: SelfossModel.Item) = private fun updateDBItem(item: SelfossModel.Item) =
db.itemsQueries.updateItem(item.datetime, item.title.getHtmlDecoded(), item.content, item.unread, item.starred, item.thumbnail, item.icon, item.link, item.sourcetitle, item.tags.joinToString(","), item.id.toString()) db.itemsQueries.updateItem(item.datetime, item.title.getHtmlDecoded(), item.content, item.unread, item.starred, item.thumbnail, item.icon, item.link, item.sourcetitle, item.tags.joinToString(","), item.id.toString())
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item>? {
try { try {
val newItems = getMaxItemsForBackground(ItemType.UNREAD) val newItems = getMaxItemsForBackground(ItemType.UNREAD)
val allItems = getMaxItemsForBackground(ItemType.ALL) val allItems = getMaxItemsForBackground(ItemType.ALL)
@@ -444,6 +471,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
return emptyList() return emptyList()
} }
// TODO: Add tests
suspend fun handleDBActions() { suspend fun handleDBActions() {
val actions: List<ACTION> = getDBActions() val actions: List<ACTION> = getDBActions()

View File

@@ -34,7 +34,6 @@ class AppSettingsService {
private var _fontSize: Int? = null private var _fontSize: Int? = null
private var _staticBar: Boolean? = null private var _staticBar: Boolean? = null
private var _darkTheme: Boolean? = null
private var _font: String = "" private var _font: String = ""
@@ -308,17 +307,6 @@ class AppSettingsService {
return _staticBar == true return _staticBar == true
} }
private fun refreshDarkThemeEnabled() {
_darkTheme = settings.getBoolean("dark_theme", false)
}
fun isDarkThemeEnabled(): Boolean {
if (_darkTheme != null) {
refreshDarkThemeEnabled()
}
return _darkTheme == true
}
private fun refreshFont() { private fun refreshFont() {
_font = settings.getString("reader_font", "") _font = settings.getString("reader_font", "")
} }
@@ -358,7 +346,6 @@ class AppSettingsService {
refreshFontSize() refreshFontSize()
refreshFont() refreshFont()
refreshStaticBarEnabled() refreshStaticBarEnabled()
refreshDarkThemeEnabled()
} }
fun refreshLoginInformation( fun refreshLoginInformation(

View File

@@ -65,6 +65,6 @@ fun SelfossModel.Item.toEntity(): ITEM =
this.thumbnail, this.thumbnail,
this.icon, this.icon,
this.link, this.link,
this.title.getHtmlDecoded(), this.sourcetitle.getHtmlDecoded(),
this.tags.joinToString(",") this.tags.joinToString(",")
) )