Compare commits

..

19 Commits

Author SHA1 Message Date
aminecmi
bf1b680b4a Daark theme working.
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-27 10:21:42 +02:00
Amine Louveau
d81fb79b4f Merge pull request 'enhancement/too-many-calls' (#68) from enhancement/too-many-calls into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/68
2022-09-26 18:26:22 +00:00
aminecmi
144067d5b6 This helps a lot.
Some checks reported errors
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build was killed
2022-09-26 16:16:40 +02:00
aminecmi
8106faa45c Only fetch intems if item caching is enabled. 2022-09-26 16:05:07 +02:00
aminecmi
d9ef301e0f Warning. 2022-09-26 15:58:05 +02:00
aminecmi
90b52232ab Updating ktor and changing the engine. This speeds things a little bit too. 2022-09-26 15:54:11 +02:00
aminecmi
0f000ea359 Only sort if the data is from the DB. This speeds things a little bit. 2022-09-26 15:53:39 +02:00
aminecmi
fb8f81a4c8 One call less.
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-26 14:05:57 +02:00
Amine Louveau
a76b3dd2a9 Merge pull request 'Theme refresh' (#65) from feature/55 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/65
2022-09-26 06:54:28 +00:00
aminecmi
91aed5a777 Hacky way to fix #55.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-09-25 22:19:47 +02:00
aminecmi
8823cc6c6c Cleaning. 2022-09-25 21:39:57 +02:00
Amine Louveau
74ef4da15b Merge pull request '2.18 serialization issues fix. Fixes #61.' (#64) from bugfix/old-version-serialization 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/64
2022-09-24 20:16:39 +00:00
aminecmi
bd96c67788 2.18 serialization issues fix. Fixes #61.
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/pr Build is passing
continuous-integration/drone Build was killed
2022-09-24 21:55:25 +02:00
Amine Louveau
da71de6806 Merge pull request 'bugfix/ktor-404' (#62) from bugfix/ktor-404 into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/62
2022-09-24 14:55:44 +00:00
aminecmi
0264da8ccc Fixing #54.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-09-24 15:00:33 +02:00
aminecmi
270d959ee0 Cleaning.
Some checks are pending
continuous-integration/drone/push Build is running
2022-09-24 14:55:06 +02:00
aminecmi
6d11dfb80c Fixing #59. 2022-09-24 14:43:24 +02:00
aminecmi
4184bbb900 Update gradle. 2022-09-24 13:54:48 +02:00
aminecmi
4c12c9d570 added badge.
All checks were successful
continuous-integration/drone/push Build is passing
2022-09-14 14:54:13 +02:00
37 changed files with 280 additions and 361 deletions

View File

@@ -1,4 +1,4 @@
# ReaderForSelfoss-multiplatform
# ReaderForSelfoss-multiplatform [![Build Status](https://build.amine-louveau.fr/api/badges/Louvorg/ReaderForSelfoss-multiplatform/status.svg)](https://build.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/readerforselfoss/localized.svg)](https://crowdin.com/project/readerforselfoss)

View File

@@ -30,7 +30,7 @@ fun gitVersion(): String {
println("Tag found on current commit")
execWithOutput("git -C ../ describe --contains HEAD")
}
return process.replace("'", "").substring(1).replace("\\.", "").trim()
return process.replace("^0", "").replace("'", "").substring(1).replace("\\.", "").trim()
}
fun versionCodeFromGit(): Int {
@@ -101,6 +101,7 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
namespace = "bou.amine.apps.readerforselfossv2.android"
}
@@ -166,8 +167,6 @@ dependencies {
implementation("com.mikepenz:materialdrawer:8.4.5")
// Themes
implementation("com.52inc:scoops:1.0.0")
implementation("com.jaredrummler:colorpicker:1.1.0")
implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
// Pager

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="bou.amine.apps.readerforselfossv2.android">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

View File

@@ -9,11 +9,10 @@ import androidx.constraintlayout.widget.ConstraintLayout
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.ftinc.scoop.Scoop
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -42,10 +41,6 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
setContentView(view)
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
val drawable = binding.nameInput.background
drawable.setTint(appColors.colorAccent)
@@ -84,7 +79,7 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
super.onResume()
val baseUrl = appSettingsService.getBaseUrl()
if (baseUrl.isEmpty() || !baseUrl.isBaseUrlValid(this@AddSourceActivity)) {
if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid()) {
mustLoginToAddSource()
} else {
handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer)

View File

@@ -15,6 +15,9 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog
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.core.view.doOnNextLayout
import androidx.drawerlayout.widget.DrawerLayout
@@ -43,7 +46,6 @@ import com.ashokvarma.bottomnavigation.BottomNavigationItem
import com.ashokvarma.bottomnavigation.TextBadgeItem
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.ftinc.scoop.Scoop
import com.mikepenz.aboutlibraries.LibsBuilder
import com.mikepenz.materialdrawer.holder.BadgeStyle
import com.mikepenz.materialdrawer.holder.ColorHolder
@@ -95,6 +97,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private val settingsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
appSettingsService.refreshUserSettings()
AppCompatDelegate.setDefaultNightMode(if (appSettingsService.isDarkThemeEnabled()) MODE_NIGHT_YES else MODE_NIGHT_NO)
}
override val di by closestDI()
@@ -126,8 +129,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
setContentView(view)
handleThemeBinding()
setSupportActionBar(binding.toolBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(true)
@@ -142,11 +143,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
handleSwipeRefreshLayout()
getElementsAccordingToTab()
CoroutineScope(Dispatchers.Main).launch {
repository.tryToCacheItemsAndGetNewOnes()
if (appSettingsService.isItemCachingEnabled()) {
CoroutineScope(Dispatchers.Main).launch {
repository.tryToCacheItemsAndGetNewOnes()
}
}
}
@@ -233,27 +234,24 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
tabNewBadge = TextBadgeItem()
.setText("")
.setHideOnSelect(false).hide(false)
.setBackgroundColor(appColors.colorPrimary)
tabArchiveBadge = TextBadgeItem()
.setText("")
.setHideOnSelect(false).hide(false)
.setBackgroundColor(appColors.colorPrimary)
tabStarredBadge = TextBadgeItem()
.setText("")
.setHideOnSelect(false).hide(false)
.setBackgroundColor(appColors.colorPrimary)
val tabNew =
BottomNavigationItem(
R.drawable.ic_tab_fiber_new_black_24dp,
getString(R.string.tab_new)
).setActiveColor(appColors.colorAccent)
)
.setBadgeItem(tabNewBadge)
val tabArchive =
BottomNavigationItem(
R.drawable.ic_tab_archive_black_24dp,
getString(R.string.tab_read)
).setActiveColor(appColors.colorAccentDark)
)
.setBadgeItem(tabArchiveBadge)
val tabStarred =
BottomNavigationItem(
@@ -269,7 +267,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
.setFirstSelectedPosition(0)
.initialise()
binding.bottomBar.setMode(BottomNavigationBar.MODE_SHIFTING)
binding.bottomBar.setBackgroundStyle(BottomNavigationBar.BACKGROUND_STYLE_STATIC)
if (fromTabShortcut) {
binding.bottomBar.selectTab(elementsShown.position - 1)
@@ -283,8 +280,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
handleDrawerItems()
handleThemeUpdate()
reloadLayoutManager()
if (appSettingsService.isInfiniteLoadingEnabled()) {
@@ -309,20 +304,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
customTabActivityHelper.unbindCustomTabsService(this)
}
private fun handleThemeBinding() {
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolBar)
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
}
private fun handleThemeUpdate() {
val scoop = Scoop.getInstance()
scoop.update(Toppings.PRIMARY.value, appColors.colorPrimary)
scoop.update(Toppings.PRIMARY_DARK.value, appColors.colorPrimaryDark)
}
private fun initDrawer() {
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {

View File

@@ -12,9 +12,10 @@ import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.mikepenz.aboutlibraries.LibsBuilder
@@ -41,6 +42,8 @@ class LoginActivity : AppCompatActivity(), DIAware {
appColors = AppColors(this@LoginActivity)
super.onCreate(savedInstanceState)
AppCompatDelegate.setDefaultNightMode(if (appSettingsService.isDarkThemeEnabled()) AppCompatDelegate.MODE_NIGHT_YES else AppCompatDelegate.MODE_NIGHT_NO)
binding = ActivityLoginBinding.inflate(layoutInflater)
val view = binding.root
@@ -115,14 +118,14 @@ class LoginActivity : AppCompatActivity(), DIAware {
binding.passwordView.error = null
// Store values at the time of the login attempt.
val url = binding.urlView.text.toString()
val login = binding.loginView.text.toString()
val password = binding.passwordView.text.toString()
val url = binding.urlView.text.toString().trim()
val login = binding.loginView.text.toString().trim()
val password = binding.passwordView.text.toString().trim()
var cancel = false
var focusView: View? = null
if (!url.isBaseUrlValid(this@LoginActivity)) {
if (url.isBaseUrlInvalid()) {
binding.urlView.error = getString(R.string.login_url_problem)
focusView = binding.urlView
cancel = true

View File

@@ -21,7 +21,6 @@ import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.ftinc.scoop.Scoop
import com.github.ln_12.library.ConnectivityStatus
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
@@ -54,8 +53,6 @@ class MyApp : MultiDexApplication(), DIAware {
initDrawerImageLoader()
initTheme()
tryToHandleBug()
handleNotificationChannels()
@@ -117,14 +114,6 @@ class MyApp : MultiDexApplication(), DIAware {
})
}
private fun initTheme() {
Scoop.waffleCone()
.addFlavor(getString(R.string.default_theme), R.style.NoBar, true)
.addFlavor(getString(R.string.default_dark_theme), R.style.NoBarDark, false)
.setSharedPreferences(PreferenceManager.getDefaultSharedPreferences(this))
.initialize()
}
private fun tryToHandleBug() {
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()

View File

@@ -17,7 +17,6 @@ import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.ftinc.scoop.Scoop
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -62,10 +61,6 @@ class ReaderActivity : AppCompatActivity(), DIAware {
setContentView(view)
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolBar)
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
setSupportActionBar(binding.toolBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)

View File

@@ -12,7 +12,6 @@ import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import com.ftinc.scoop.Scoop
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -33,10 +32,6 @@ class SourcesActivity : AppCompatActivity(), DIAware {
binding = ActivitySourcesBinding.inflate(layoutInflater)
val view = binding.root
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
super.onCreate(savedInstanceState)
setContentView(view)

View File

@@ -30,7 +30,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
updateItems(this.items)
}
private fun unmarkSnackbar(i: SelfossModel.Item, position: Int) {
private fun unmarkSnackbar(position: Int) {
val s = Snackbar
.make(
app.findViewById(R.id.coordLayout),
@@ -87,7 +87,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
notifyItemChanged(position)
}
if (showSnackbar) {
unmarkSnackbar(i, position)
unmarkSnackbar(position)
}
}

View File

@@ -268,8 +268,8 @@ class ArticleFragment : Fragment(), DIAware {
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) {
if (repository.isNetworkAvailable()) {
binding.progressBar.visibility = View.VISIBLE
val parser = MercuryApi()
binding.progressBar.visibility = View.VISIBLE
val parser = MercuryApi()
parser.parseUrl(url).enqueue(
object : Callback<ParsedContent> {

View File

@@ -22,7 +22,6 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBin
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.ftinc.scoop.Scoop
private const val TITLE_TAG = "settingsActivityTitle"
@@ -31,15 +30,8 @@ class SettingsActivity : AppCompatActivity(),
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("dark_theme", false)) {
setTheme(R.style.NoBarDark)
}
val binding = ActivitySettingsBinding.inflate(layoutInflater)
val scoop = Scoop.getInstance()
scoop.bind(this, Toppings.PRIMARY.value, binding.toolbar)
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
setContentView(binding.root)
if (savedInstanceState == null) {
supportFragmentManager
@@ -171,20 +163,6 @@ class SettingsActivity : AppCompatActivity(),
setPreferencesFromResource(R.xml.pref_theme, rootKey)
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.settings_theme, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.clear) {
AppColors.resetColors()
requireActivity().recreate()
}
return super.onOptionsItemSelected(item)
}
}
class LinksPreferenceFragment : PreferenceFragmentCompat() {

View File

@@ -7,66 +7,20 @@ import com.russhwolf.settings.Settings
class AppColors(a: Activity) {
@ColorInt val colorPrimary: Int
@ColorInt val colorPrimaryDark: Int
@ColorInt val colorAccent: Int
@ColorInt val colorAccentDark: Int
@ColorInt val colorBackground: Int
@ColorInt val textColor: Int
@ColorInt val colorPrimary: Int = a.resources.getColor(R.color.colorPrimary)
@ColorInt val colorPrimaryDark: Int = a.resources.getColor(R.color.colorPrimaryDark)
@ColorInt val colorAccent: Int = a.resources.getColor(R.color.colorAccent)
@ColorInt val colorAccentDark: Int = a.resources.getColor(R.color.colorAccentDark)
val isDarkTheme: Boolean
init {
val settings = Settings()
colorPrimary =
settings.getInt(
"color_primary",
a.resources.getColor(R.color.colorPrimary)
)
colorPrimaryDark =
settings.getInt(
"color_primary_dark",
a.resources.getColor(R.color.colorPrimaryDark)
)
colorAccent =
settings.getInt(
"color_accent",
a.resources.getColor(R.color.colorAccent)
)
colorAccentDark =
settings.getInt(
"color_accent_dark",
a.resources.getColor(R.color.colorAccentDark)
)
isDarkTheme =
settings.getBoolean(
"dark_theme",
false
)
colorBackground = if (isDarkTheme) {
a.setTheme(R.style.NoBarDark)
a.resources.getColor(R.color.darkBackground)
} else {
a.setTheme(R.style.NoBar)
a.resources.getColor(R.color.grey_50)
}
textColor = if (isDarkTheme) {
a.resources.getColor(R.color.white)
} else {
a.resources.getColor(R.color.grey_900)
}
}
companion object {
fun resetColors() {
val settings = Settings()
settings.remove("color_primary")
settings.remove("color_primary_dark")
settings.remove("color_accent")
settings.remove("color_accent_dark")
settings.remove("dark_theme")
}
}
}

View File

@@ -163,7 +163,7 @@ private fun openInBrowser(linkDecoded: String, app: Activity) {
fun String.isUrlValid(): Boolean =
this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
fun String.isBaseUrlValid(ctx: Context): Boolean {
fun String.isBaseUrlInvalid(): Boolean {
val baseUrl = this.toHttpUrlOrNull()
var existsAndEndsWithSlash = false
if (baseUrl != null) {
@@ -171,7 +171,7 @@ fun String.isBaseUrlValid(ctx: Context): Boolean {
existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1]
}
return Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash
return !(Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash)
}
fun Context.openInBrowserAsNewTask(i: SelfossModel.Item) {

View File

@@ -1,8 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@color/ic_launcher_background"/>
<item>
<shape android:shape="rectangle" >
<solid android:color="?attr/colorSurface" />
</shape>
</item>
<item>
<bitmap

View File

@@ -18,8 +18,8 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
/>
</com.google.android.material.appbar.AppBarLayout>
@@ -107,8 +107,7 @@
app:layout_constraintTop_toBottomOf="@+id/tags"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_height="40dp"
android:theme="@style/App.Spinner"/>
android:layout_height="40dp"/>
<Button
android:text="@string/add_source_save"

View File

@@ -32,8 +32,8 @@
android:id="@+id/toolBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
/>
</com.google.android.material.appbar.AppBarLayout>
@@ -78,10 +78,12 @@
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.ashokvarma.bottomnavigation.BottomNavigationBar
android:layout_gravity="bottom"
android:id="@+id/bottomBar"
android:layout_width="match_parent"
android:layout_height="60dp"/>
android:layout_height="60dp"
android:layout_gravity="bottom"
app:bnbActiveColor="@color/colorAccent"
app:bnbBackgroundColor="?attr/bottomBarBackground" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.mikepenz.materialdrawer.widget.MaterialDrawerSliderView

View File

@@ -16,8 +16,8 @@
android:id="@+id/toolBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="?attr/toolbarPopupTheme"
app:theme="@style/ToolBarStyle" />
/>
</com.google.android.material.appbar.AppBarLayout>

View File

@@ -14,8 +14,8 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
/>
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout

View File

@@ -17,8 +17,8 @@
android:id="@+id/toolBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="?attr/toolbarPopupTheme"
app:theme="@style/ToolBarStyle" />
/>
</com.google.android.material.appbar.AppBarLayout>

View File

@@ -12,7 +12,7 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ToolBarStyle" />
/>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout

View File

@@ -14,8 +14,8 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" />
/>
</com.google.android.material.appbar.AppBarLayout>

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/clear"
android:icon="@drawable/ic_history_white_24dp"
android:title="@string/drawer_action_clear"
app:showAsAction="ifRoom" />
</menu>

View File

@@ -0,0 +1,13 @@
<resources>
<style name="NoBar" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorAccentDark">@color/colorAccentDark</item>
<item name="cardBackgroundColor">@color/white</item>
<item name="materialDrawerHeaderStyle">@style/Widget.MaterialDrawerHeaderStyle</item>
<item name="preferenceTheme">@style/PreferenceStyle</item>
<item name="android:statusBarColor">#282828</item>
<item name="bottomBarBackground">#282828</item>
</style>
</resources>

View File

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

View File

@@ -11,6 +11,4 @@
<color name="refresh_progress_1">@color/colorAccentDark</color>
<color name="refresh_progress_2">@color/colorAccent</color>
<color name="refresh_progress_3">@color/pink</color>
<color name="darkBackground">#303030</color>
</resources>

View File

@@ -4,70 +4,20 @@
<item name="android:windowBackground">@drawable/background_splash</item>
</style>
<style name="NoBar" parent="Theme.MaterialComponents.Light.NoActionBar">
<style name="NoBar" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorAccentDark">@color/colorAccentDark</item>
<item name="cardBackgroundColor">@color/white</item>
<item name="android:colorBackground">@color/grey_50</item>
<item name="colorSurface">@color/grey_50</item>
<item name="android:textColorPrimary">@color/grey_900</item>
<item name="android:textColorSecondary">@color/grey_400</item>
<item name="materialDrawerStyle">@style/App.materialDrawerStyle</item>
<item name="materialDrawerHeaderStyle">@style/Widget.MaterialDrawerHeaderStyle</item>
<item name="toolbarPopupTheme">@style/ThemeOverlay.AppCompat.Light</item>
<item name="preferenceTheme">@style/PreferenceStyle</item>
<item name="android:statusBarColor">?attr/colorPrimary</item>
<item name="bottomBarBackground">@color/white</item>
</style>
<style name="NoBarDark" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorAccentDark">@color/colorAccentDark</item>
<item name="cardBackgroundColor">@color/grey_800</item>
<item name="android:colorBackground">@color/darkBackground</item>
<item name="colorSurface">@color/darkBackground</item>
<item name="alertDialogTheme">@style/AlertDialogDark</item>
<item name="bnbBackgroundColor">@color/grey_900</item>
<item name="android:textColorPrimary">@color/white</item>
<item name="android:textColorSecondary">@color/grey_600</item>
<item name="materialDrawerStyle">@style/App.materialDrawerStyle</item>
<item name="materialDrawerHeaderStyle">@style/Widget.MaterialDrawerHeaderStyle</item>
<item name="toolbarPopupTheme">@style/ThemeOverlay.AppCompat.Dark</item>
<item name="preferenceTheme">@style/PreferenceStyle</item>
</style>
<!-- ToolBar -->
<style name="ToolBarStyle" parent="Theme.AppCompat">
<item name="android:textColorPrimary">@color/white</item>
<item name="android:textColorSecondary">@color/white</item>
<item name="actionMenuTextColor">@color/white</item>
<!--<item name="actionOverflowButtonStyle">@style/ActionButtonOverflowStyle</item>
<item name="drawerArrowStyle">@style/DrawerArrowStyle</item>-->
</style>
<!-- Material Drawer Theme -->
<style name="App.materialDrawerStyle" parent="@style/Widget.MaterialDrawerStyle">
<item name="materialDrawerPrimaryIcon">?android:textColorPrimary</item>
<item name="materialDrawerSecondaryIcon">?android:textColorPrimary</item>
<item name="materialDrawerSecondaryText">?android:textColorPrimary</item>
</style>
<!-- Preference Theme -->
<style name="PreferenceStyle" parent="@style/PreferenceThemeOverlay">
<item name="android:tint">?android:textColorPrimary</item>
<item name="android:tint">?attr/colorOnSurface</item>
</style>
<!-- Spinner Theme -->
<style name="App.Spinner" parent="Widget.AppCompat.Light.DropDownItem.Spinner">
<item name="android:textColor">?android:textColorPrimary</item>
</style>
<!-- Alert dialog Theme -->
<style name="AlertDialogDark" parent="Theme.MaterialComponents.Dialog">
<item name="android:background">@color/darkBackground</item>
</style>
</resources>

View File

@@ -6,7 +6,7 @@
</PreferenceCategory>
<EditTextPreference
android:defaultValue="200"
android:defaultValue="20"
android:inputType="number"
android:key="prefer_api_items_number"
android:selectAllOnFocus="true"

View File

@@ -8,28 +8,4 @@
android:key="dark_theme"
app:iconSpaceReserved="false"
android:title="Dark theme" />
<com.jaredrummler.android.colorpicker.ColorPreferenceCompat
android:defaultValue="@color/colorPrimary"
android:key="color_primary"
app:iconSpaceReserved="false"
android:title="Primary color"/>
<com.jaredrummler.android.colorpicker.ColorPreferenceCompat
android:defaultValue="@color/colorPrimaryDark"
android:key="color_primary_dark"
app:iconSpaceReserved="false"
android:title="Primary dark color"/>
<com.jaredrummler.android.colorpicker.ColorPreferenceCompat
android:defaultValue="@color/colorAccent"
android:key="color_accent"
app:iconSpaceReserved="false"
android:title="Accent color"/>
<com.jaredrummler.android.colorpicker.ColorPreferenceCompat
android:defaultValue="@color/colorAccentDark"
android:key="color_accent_dark"
app:iconSpaceReserved="false"
android:title="Accent dark color"/>
</PreferenceScreen>

View File

@@ -6,7 +6,7 @@ buildscript {
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
classpath("com.android.tools.build:gradle:7.2.2")
classpath("com.android.tools.build:gradle:7.3.0")
// sonarquve
classpath("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513")

View File

@@ -1,6 +1,6 @@
#Wed Feb 09 17:05:19 CET 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@@ -28,12 +28,12 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core:2.0.1")
implementation("io.ktor:ktor-client-content-negotiation:2.0.1")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.0.1")
implementation("io.ktor:ktor-client-logging:2.0.1")
implementation("io.ktor:ktor-client-core:2.1.1")
implementation("io.ktor:ktor-client-content-negotiation:2.1.1")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.1.1")
implementation("io.ktor:ktor-client-logging:2.1.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("io.ktor:ktor-client-auth:2.0.1")
implementation("io.ktor:ktor-client-auth:2.1.1")
implementation("org.jsoup:jsoup:1.14.3")
//Dependency Injection
@@ -60,7 +60,7 @@ kotlin {
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-android:2.0.1")
implementation("io.ktor:ktor-client-okhttp:2.1.1")
// Sql
implementation(SqlDelight.android)
@@ -94,7 +94,7 @@ kotlin {
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
dependencies {
implementation("io.ktor:ktor-client-ios:2.0.1")
implementation("io.ktor:ktor-client-ios:2.1.1")
}
//iosSimulatorArm64Test.dependsOn(this)
}
@@ -112,6 +112,7 @@ android {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
namespace = "bou.amine.apps.readerforselfossv2"
}
sqldelight {

View File

@@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="bou.amine.apps.readerforselfossv2" />
<manifest />

View File

@@ -2,7 +2,14 @@ package bou.amine.apps.readerforselfossv2.model
import bou.amine.apps.readerforselfossv2.utils.DateUtils
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
class SelfossModel {
@@ -50,6 +57,7 @@ class SelfossModel {
data class Source(
val id: Int,
val title: String,
@Serializable(with = TagsListSerializer::class)
val tags: List<String>,
val spout: String,
val error: String,
@@ -62,12 +70,15 @@ class SelfossModel {
val datetime: String,
val title: String,
val content: String,
@Serializable(with = BooleanSerializer::class)
var unread: Boolean,
@Serializable(with = BooleanSerializer::class)
var starred: Boolean,
val thumbnail: String?,
val icon: String?,
val link: String,
val sourcetitle: String,
@Serializable(with = TagsListSerializer::class)
val tags: List<String>
) {
// TODO: maybe find a better way to handle these kind of urls
@@ -105,4 +116,48 @@ class SelfossModel {
return this
}
}
}
// TODO: this seems to be super slow.
object TagsListSerializer : KSerializer<List<String>> {
override fun deserialize(decoder: Decoder): List<String> {
return when(val json = ((decoder as JsonDecoder).decodeJsonElement())) {
is JsonArray -> json.toList().map { it.toString() }
else -> json.toString().split(",")
}
}
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: List<String>) {
TODO("Not yet implemented")
}
}
object BooleanSerializer : KSerializer<Boolean> {
override fun deserialize(decoder: Decoder): Boolean {
val json = ((decoder as JsonDecoder).decodeJsonElement()).jsonPrimitive
return json.booleanOrNull ?: json.int == 1
}
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("b", PrimitiveKind.BOOLEAN)
override fun serialize(encoder: Encoder, value: Boolean) {
TODO("Not yet implemented")
}
}
class StatusAndData<T>(val success: Boolean, val data: T? = null) {
companion object {
fun <T> succes(d: T): StatusAndData<T> {
return StatusAndData(true, d)
}
fun <T> error(): StatusAndData<T> {
return StatusAndData(false)
}
}
}
}

View File

@@ -47,7 +47,8 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
// TODO: Use the updatedSince parameter
var fetchedItems: List<SelfossModel.Item>? = null
var fetchedItems: SelfossModel.StatusAndData<List<SelfossModel.Item>> = SelfossModel.StatusAndData.error()
var fromDB = false
if (isNetworkAvailable()) {
fetchedItems = api.getItems(
displayedItems.type,
@@ -59,23 +60,28 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
)
} else {
if (appSettingsService.isItemCachingEnabled()) {
fetchedItems = getDBItems().filter {
displayedItems == ItemType.ALL ||
(it.unread && displayedItems == ItemType.UNREAD) ||
(it.starred && displayedItems == ItemType.STARRED)
}.map { it.toView() }
fromDB = true
fetchedItems = SelfossModel.StatusAndData.succes(
getDBItems().filter {
displayedItems == ItemType.ALL ||
(it.unread && displayedItems == ItemType.UNREAD) ||
(it.starred && displayedItems == ItemType.STARRED)
}.map { it.toView() }
)
}
}
if (fetchedItems != null) {
items = ArrayList(fetchedItems)
sortItems()
if (fetchedItems.success && fetchedItems.data != null) {
items = ArrayList(fetchedItems.data!!)
if (fromDB) {
items.sortByDescending { dateUtils.parseDate(it.datetime) }
}
}
return items
}
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
var fetchedItems: List<SelfossModel.Item>? = null
var fetchedItems: SelfossModel.StatusAndData<List<SelfossModel.Item>> = SelfossModel.StatusAndData.error()
if (isNetworkAvailable()) {
val offset = items.size
fetchedItems = api.getItems(
@@ -88,16 +94,15 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
)
} // When using the db cache, we load everything the first time, so there should be nothing more to load.
if (fetchedItems != null) {
items.addAll(fetchedItems)
sortItems()
if (fetchedItems.success && fetchedItems.data != null) {
items.addAll(fetchedItems.data!!)
}
return items
}
private suspend fun getMaxItemsForBackground(itemType: ItemType): List<SelfossModel.Item>? {
private suspend fun getMaxItemsForBackground(itemType: ItemType): List<SelfossModel.Item> {
return if (isNetworkAvailable()) {
api.getItems(
val items = api.getItems(
itemType.type,
0,
tagFilter?.tag,
@@ -106,23 +111,24 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
null,
200
)
return if (items.success && items.data != null) {
items.data
} else {
emptyList()
}
} else {
emptyList()
}
}
private fun sortItems() {
items.sortByDescending { dateUtils.parseDate(it.datetime) }
}
suspend fun reloadBadges(): Boolean {
var success = false
if (isNetworkAvailable()) {
val response = api.stats()
if (response != null) {
badgeUnread = response.unread
badgeAll = response.total
badgeStarred = response.starred
if (response.success && response.data != null) {
badgeUnread = response.data.unread
badgeAll = response.data.total
badgeStarred = response.data.starred
success = true
}
} else {
@@ -138,10 +144,10 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun getTags(): List<SelfossModel.Tag>? {
return if (isNetworkAvailable()) {
val apiTags = api.tags()
if (apiTags != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) {
resetDBTagsWithData(apiTags)
if (apiTags.success && apiTags.data != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) {
resetDBTagsWithData(apiTags.data)
}
apiTags
apiTags.data
} else {
getDBTags().map { it.toView() }
}
@@ -149,7 +155,12 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun getSpouts(): Map<String, SelfossModel.Spout>? {
return if (isNetworkAvailable()) {
api.spouts()
val spouts = api.spouts()
return if (spouts.success && spouts.data != null) {
spouts.data
} else {
emptyMap() // TODO: do something here
}
} else {
throw NetworkUnavailableException()
}
@@ -158,10 +169,10 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun getSources(): ArrayList<SelfossModel.Source>? {
return if (isNetworkAvailable()) {
val apiSources = api.sources()
if (apiSources != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) {
resetDBSourcesWithData(apiSources)
if (apiSources.success && apiSources.data != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) {
resetDBSourcesWithData(apiSources.data)
}
apiSources
apiSources.data
} else {
ArrayList(getDBSources().map { it.toView() })
}
@@ -178,7 +189,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun markAsReadById(id: Int): Boolean {
return if (isNetworkAvailable()) {
api.markAsRead(id.toString())?.isSuccess == true
api.markAsRead(id.toString())?.isSuccess
} else {
insertDBAction(id.toString(), read = true)
true
@@ -197,7 +208,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun unmarkAsReadById(id: Int): Boolean {
return if (isNetworkAvailable()) {
api.unmarkAsRead(id.toString())?.isSuccess == true
api.unmarkAsRead(id.toString())?.isSuccess
} else {
insertDBAction(id.toString(), unread = true)
true
@@ -215,7 +226,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun starrById(id: Int): Boolean {
return if (isNetworkAvailable()) {
api.starr(id.toString())?.isSuccess == true
api.starr(id.toString())?.isSuccess
} else {
insertDBAction(id.toString(), starred = true)
true
@@ -233,7 +244,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private suspend fun unstarrById(id: Int): Boolean {
return if (isNetworkAvailable()) {
api.unstarr(id.toString())?.isSuccess == true
api.unstarr(id.toString())?.isSuccess
} else {
insertDBAction(id.toString(), starred = true)
true
@@ -243,7 +254,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
var success = false
if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true) {
if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess) {
success = true
for (item in items) {
markAsReadLocally(item)
@@ -332,7 +343,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
suspend fun updateRemote(): Boolean {
return if (isNetworkAvailable()) {
api.update()?.equals("finished") ?: false
api.update()?.equals("finished")
} else {
false
}
@@ -365,8 +376,8 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
if (isNetworkAvailable()) {
val fetchedVersion = api.version()
if (fetchedVersion != null && fetchedVersion.getApiMajorVersion() != apiMajorVersion) {
appSettingsService.updateApiVersion(fetchedVersion.getApiMajorVersion())
if (fetchedVersion.success && fetchedVersion.data != null && fetchedVersion.data.getApiMajorVersion() != apiMajorVersion) {
appSettingsService.updateApiVersion(fetchedVersion.data.getApiMajorVersion())
}
}
}
@@ -383,7 +394,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
fun getDBSources(): List<SOURCE> = db.sourcesQueries.sources().executeAsList()
fun resetDBTagsWithData(tagEntities: List<SelfossModel.Tag>) {
private fun resetDBTagsWithData(tagEntities: List<SelfossModel.Tag>) {
db.tagsQueries.deleteAllTags()
db.tagsQueries.transaction {
@@ -393,7 +404,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
}
}
fun resetDBSourcesWithData(sources: List<SelfossModel.Source>) {
private fun resetDBSourcesWithData(sources: List<SelfossModel.Source>) {
db.sourcesQueries.deleteAllSources()
db.sourcesQueries.transaction {
@@ -425,7 +436,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
val newItems = getMaxItemsForBackground(ItemType.UNREAD)
val allItems = getMaxItemsForBackground(ItemType.ALL)
val starredItems = getMaxItemsForBackground(ItemType.STARRED)
insertDBItems(newItems.orEmpty() + allItems.orEmpty() + starredItems.orEmpty())
insertDBItems(newItems + allItems + starredItems)
return newItems
} catch (e: Throwable) {
// We do nothing

View File

@@ -10,6 +10,7 @@ import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
@@ -34,7 +35,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
appSettingsService.logApiCalls(message)
}
}
level = LogLevel.ALL
level = LogLevel.INFO
}
install(HttpTimeout) {
requestTimeoutMillis = appSettingsService.getApiTimeout()
@@ -65,11 +66,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client = createHttpClient()
}
suspend fun login(): SelfossModel.SuccessResponse? =
client.get(url("/login")) {
suspend fun login(): SelfossModel.SuccessResponse =
maybeResponse(client.get(url("/login")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun getItems(
type: String,
@@ -79,8 +80,8 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
search: String?,
updatedSince: String?,
items: Int? = null
): List<SelfossModel.Item>? =
client.get(url("/items")) {
): SelfossModel.StatusAndData<List<SelfossModel.Item>> =
bodyOrFailure(client.get(url("/items")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
parameter("type", type)
@@ -90,74 +91,74 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("updatedsince", updatedSince)
parameter("items", items ?: appSettingsService.getItemsNumber())
parameter("offset", offset)
}.body()
})
suspend fun stats(): SelfossModel.Stats? =
client.get(url("/stats")) {
suspend fun stats(): SelfossModel.StatusAndData<SelfossModel.Stats> =
bodyOrFailure(client.get(url("/stats")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun tags(): List<SelfossModel.Tag>? =
client.get(url("/tags")) {
suspend fun tags(): SelfossModel.StatusAndData<List<SelfossModel.Tag>> =
bodyOrFailure(client.get(url("/tags")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun update(): String? =
client.get(url("/update")) {
suspend fun update(): SelfossModel.StatusAndData<String> =
bodyOrFailure(client.get(url("/update")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun spouts(): Map<String, SelfossModel.Spout>? =
client.get(url("/sources/spouts")) {
suspend fun spouts(): SelfossModel.StatusAndData<Map<String, SelfossModel.Spout>> =
bodyOrFailure(client.get(url("/sources/spouts")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun sources(): ArrayList<SelfossModel.Source>? =
client.get(url("/sources/list")) {
suspend fun sources(): SelfossModel.StatusAndData<ArrayList<SelfossModel.Source>> =
bodyOrFailure(client.get(url("/sources/list")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun version(): SelfossModel.ApiVersion? =
client.get(url("/api/about")).body()
suspend fun version(): SelfossModel.StatusAndData<SelfossModel.ApiVersion> =
bodyOrFailure(client.get(url("/api/about")))
suspend fun markAsRead(id: String): SelfossModel.SuccessResponse? =
client.post(url("/mark/$id")) {
suspend fun markAsRead(id: String): SelfossModel.SuccessResponse =
maybeResponse(client.post(url("/mark/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse? =
client.post(url("/unmark/$id")) {
suspend fun unmarkAsRead(id: String): SelfossModel.SuccessResponse =
maybeResponse(client.post(url("/unmark/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun starr(id: String): SelfossModel.SuccessResponse? =
client.post(url("/starr/$id")) {
suspend fun starr(id: String): SelfossModel.SuccessResponse =
maybeResponse(client.post(url("/starr/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun unstarr(id: String): SelfossModel.SuccessResponse? =
client.post(url("/unstarr/$id")) {
suspend fun unstarr(id: String): SelfossModel.SuccessResponse =
maybeResponse(client.post(url("/unstarr/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun markAllAsRead(ids: List<String>): SelfossModel.SuccessResponse? =
client.submitForm(
suspend fun markAllAsRead(ids: List<String>): SelfossModel.SuccessResponse =
maybeResponse(client.submitForm(
url = url("/mark"),
formParameters = Parameters.build {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
ids.map { append("ids[]", it) }
}
).body()
))
suspend fun createSourceForVersion(
title: String,
@@ -166,12 +167,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
tags: String,
filter: String,
version: Int
): SelfossModel.SuccessResponse? =
if (version > 1) {
createSource2(title, url, spout, tags, filter)
} else {
createSource(title, url, spout, tags, filter)
}
): SelfossModel.SuccessResponse =
maybeResponse(
if (version > 1) {
createSource2(title, url, spout, tags, filter)
} else {
createSource(title, url, spout, tags, filter)
}
)
suspend fun createSource(
title: String,
@@ -179,7 +182,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
spout: String,
tags: String,
filter: String
): SelfossModel.SuccessResponse? =
): HttpResponse =
client.submitForm(
url = url("/source?username=${appSettingsService.getUserName()}&password=${appSettingsService.getPassword()}"),
formParameters = Parameters.build {
@@ -189,7 +192,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
append("tags", tags)
append("filter", filter)
}
).body()
)
suspend fun createSource2(
title: String,
@@ -197,7 +200,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
spout: String,
tags: String,
filter: String
): SelfossModel.SuccessResponse? =
): HttpResponse =
client.submitForm(
url = url("/source?username=${appSettingsService.getUserName()}&password=${appSettingsService.getPassword()}"),
formParameters = Parameters.build {
@@ -207,11 +210,27 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
append("tags[]", tags)
append("filter", filter)
}
).body()
)
suspend fun deleteSource(id: Int): SelfossModel.SuccessResponse? =
client.delete(url("/source/$id")) {
suspend fun deleteSource(id: Int): SelfossModel.SuccessResponse =
maybeResponse(client.delete(url("/source/$id")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}.body()
})
suspend fun maybeResponse(r: HttpResponse): SelfossModel.SuccessResponse {
return if (r.status.isSuccess()) {
r.body()
} else {
SelfossModel.SuccessResponse(false)
}
}
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse): SelfossModel.StatusAndData<T> {
return if (r.status.isSuccess()) {
SelfossModel.StatusAndData.succes(r.body())
} else {
SelfossModel.StatusAndData.error()
}
}
}

View File

@@ -35,6 +35,7 @@ class AppSettingsService {
private var _fontSize: Int? = null
private var _staticBar: Boolean? = null
private var _darkTheme: Boolean? = null
private var _font: String = ""
@@ -88,7 +89,7 @@ class AppSettingsService {
}
fun refreshItemsNumber() {
_itemsNumber = settings.getString("prefer_api_items_number", "200").toInt()
_itemsNumber = settings.getString("prefer_api_items_number", "20").toInt()
}
fun getApiTimeout(): Long {
@@ -319,6 +320,17 @@ class AppSettingsService {
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() {
_font = settings.getString("reader_font", "")
}
@@ -359,6 +371,7 @@ class AppSettingsService {
refreshFontSize()
refreshFont()
refreshStaticBarEnabled()
refreshDarkThemeEnabled()
}
fun refreshLoginInformation(