Compare commits

...

4 Commits

Author SHA1 Message Date
aminecmi
9a5caf1b3c Settings for acra and analytics. Closes #98.
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-12-01 20:15:36 +00:00
aminecmi
7db91b0f11 Initial integration. 2022-12-01 20:15:35 +00:00
Amine Louveau
f09f731d30 Merge pull request 'Cookies login and logout.' (#103) from login into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/103
2022-11-30 10:04:13 +00:00
aminecmi
956c4341c7 Cookies login and logout.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-11-29 21:38:58 +01:00
33 changed files with 375 additions and 103 deletions

View File

@ -1,6 +1,7 @@
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
val ignoreGitVersion: String by project val ignoreGitVersion: String by project
val acraVersion = "5.9.7"
plugins { plugins {
id("com.android.application") id("com.android.application")
@ -187,6 +188,9 @@ dependencies {
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
implementation("ch.acra:acra-http:$acraVersion")
implementation("ch.acra:acra-toast:$acraVersion")
// Matomo // Matomo
implementation("com.github.matomo-org:matomo-sdk-android:4.1.4") implementation("com.github.matomo-org:matomo-sdk-android:4.1.4")
} }

View File

@ -59,11 +59,15 @@ import com.mikepenz.materialdrawer.util.updateBadge
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 org.acra.ACRA
import org.acra.ktx.sendSilentlyWithAcra
import org.acra.ktx.sendWithAcra
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.closestDI import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
import org.matomo.sdk.Tracker import org.matomo.sdk.Tracker
import org.matomo.sdk.extra.TrackHelper import org.matomo.sdk.extra.TrackHelper
import java.security.MessageDigest
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -306,6 +310,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
handleBottomBarActions() handleBottomBarActions()
handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
handleRecurringTask() handleRecurringTask()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@ -315,6 +321,26 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
getElementsAccordingToTab() getElementsAccordingToTab()
} }
private fun handleGDPRDialog(GDPRShown: Boolean) {
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
ACRA.errorReporter.putCustomData("unique_id", String(messageDigest.digest()))
if (!GDPRShown) {
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL,
"OK"
) { dialog, _ ->
appSettingsService.settings.putBoolean("GDPR_shown", true)
dialog.dismiss()
}
alertDialog.show()
}
}
private fun initDrawer() { private fun initDrawer() {
DrawerImageLoader.init(object : AbstractDrawerImageLoader() { DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) { override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {
@ -487,6 +513,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
val gdColor = try { val gdColor = try {
Color.parseColor(it.color) Color.parseColor(it.color)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
e.sendSilentlyWithAcra()
resources.getColor(R.color.colorPrimary) resources.getColor(R.color.colorPrimary)
} }
gd.setColor(gdColor) gd.setColor(gdColor)
@ -869,7 +896,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
return true return true
} }
R.id.action_disconnect -> { R.id.action_disconnect -> {
appSettingsService.clearAll() CoroutineScope(Dispatchers.Main).launch {
repository.logout()
}
val intent = Intent(this, LoginActivity::class.java) val intent = Intent(this, LoginActivity::class.java)
this.startActivity(intent) this.startActivity(intent)
this@HomeActivity.finish() this@HomeActivity.finish()

View File

@ -29,6 +29,13 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.acra.ACRA
import org.acra.ReportField
import org.acra.config.httpSender
import org.acra.config.toast
import org.acra.data.StringFormat
import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import org.kodein.di.* import org.kodein.di.*
import org.matomo.sdk.Matomo import org.matomo.sdk.Matomo
import org.matomo.sdk.Tracker import org.matomo.sdk.Tracker
@ -59,28 +66,59 @@ class MyApp : MultiDexApplication(), DIAware {
super.onCreate() super.onCreate()
Napier.base(DebugAntilog()) Napier.base(DebugAntilog())
initDrawerImageLoader() if (!ACRA.isACRASenderServiceProcess()) {
initDrawerImageLoader()
tryToHandleBug() tryToHandleBug()
handleNotificationChannels() handleNotificationChannels()
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository)) ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository))
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
viewModel.networkAvailableProvider.collect { networkAvailable -> viewModel.networkAvailableProvider.collect { networkAvailable ->
val toastMessage = if (networkAvailable) { val toastMessage = if (networkAvailable) {
repository.handleDBActions() repository.handleDBActions()
R.string.network_connectivity_retrieved R.string.network_connectivity_retrieved
} else { } else {
R.string.network_connectivity_lost R.string.network_connectivity_lost
}
Toast.makeText(
applicationContext,
toastMessage,
Toast.LENGTH_SHORT
).show()
} }
}
}
}
Toast.makeText( override fun attachBaseContext(base: Context?) {
applicationContext, super.attachBaseContext(base)
toastMessage,
Toast.LENGTH_SHORT initAcra {
).show() reportFormat = StringFormat.JSON
reportSendSuccessToast = "YES"
reportSendFailureToast = "NO !"
reportContent = listOf(
ReportField.REPORT_ID, ReportField.INSTALLATION_ID,
ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME,
ReportField.BUILD, ReportField.ANDROID_VERSION, ReportField.BRAND, ReportField.PHONE_MODEL,
ReportField.AVAILABLE_MEM_SIZE, ReportField.TOTAL_MEM_SIZE,
ReportField.STACK_TRACE, ReportField.APPLICATION_LOG, ReportField.LOGCAT,
ReportField.INITIAL_CONFIGURATION, ReportField.CRASH_CONFIGURATION, ReportField.IS_SILENT,
ReportField.USER_APP_START_DATE, ReportField.USER_COMMENT, ReportField.USER_CRASH_DATE, ReportField.USER_EMAIL, ReportField.CUSTOM_DATA)
toast {
//required
text = getString(R.string.crash_toast_text)
length = Toast.LENGTH_SHORT
}
httpSender {
uri = "https://bugs.amine-louveau.fr/report" /*best guess, you may need to adjust this*/
basicAuthLogin = "LMTlLZuazADohTCm"
basicAuthPassword = "he6ghHp83F0PYPfh"
httpMethod = HttpSender.Method.POST
} }
} }
} }

View File

@ -44,6 +44,8 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton
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 org.acra.ktx.sendSilentlyWithAcra
import org.acra.ktx.sendWithAcra
import org.kodein.di.DI import org.kodein.di.DI
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI import org.kodein.di.android.x.closestDI
@ -112,6 +114,7 @@ class ArticleFragment : Fragment(), DIAware {
typeface = try { typeface = try {
ResourcesCompat.getFont(requireContext(), resId)!! ResourcesCompat.getFont(requireContext(), resId)!!
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
e.sendSilentlyWithAcra()
// Just to be sure // Just to be sure
null null
} }
@ -217,6 +220,7 @@ class ArticleFragment : Fragment(), DIAware {
) )
} catch (e: InflateException) { } catch (e: InflateException) {
e.sendSilentlyWithAcra()
AlertDialog.Builder(requireContext()) AlertDialog.Builder(requireContext())
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message)) .setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title)) .setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
@ -264,15 +268,18 @@ class ArticleFragment : Fragment(), DIAware {
URL(response.data!!.url) URL(response.data!!.url)
url = response.data!!.url url = response.data!!.url
} catch (e: MalformedURLException) { } catch (e: MalformedURLException) {
// Mercury returned a relative url. We do nothing. // Mercury returned a relative url
e.sendSilentlyWithAcra()
} }
} catch (e: Exception) { } catch (e: Exception) {
e.sendSilentlyWithAcra()
} }
try { try {
contentText = response.data!!.content.orEmpty() contentText = response.data!!.content.orEmpty()
htmlToWebview() htmlToWebview()
} catch (e: Exception) { } catch (e: Exception) {
e.sendSilentlyWithAcra()
} }
try { try {
@ -288,13 +295,13 @@ class ArticleFragment : Fragment(), DIAware {
.apply(RequestOptions.fitCenterTransform()) .apply(RequestOptions.fitCenterTransform())
.into(binding.imageView) .into(binding.imageView)
} catch (e: Exception) { } catch (e: Exception) {
e.sendSilentlyWithAcra()
} }
} else { } else {
binding.imageView.visibility = View.GONE binding.imageView.visibility = View.GONE
} }
} catch (e: Exception) { } catch (e: Exception) {
if (context != null) { e.sendSilentlyWithAcra()
}
} }
try { try {
@ -302,20 +309,17 @@ class ArticleFragment : Fragment(), DIAware {
binding.progressBar.visibility = View.GONE binding.progressBar.visibility = View.GONE
} catch (e: Exception) { } catch (e: Exception) {
if (context != null) { e.sendSilentlyWithAcra()
}
} }
} else { } else {
try { try {
openInBrowserAfterFailing() openInBrowserAfterFailing()
} catch (e: Exception) { } catch (e: Exception) {
if (context != null) { e.sendSilentlyWithAcra()
}
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
if (context != null) { e.sendSilentlyWithAcra()
}
} }
} else { } else {
openInBrowserAfterFailing() openInBrowserAfterFailing()
@ -359,19 +363,25 @@ class ArticleFragment : Fragment(), DIAware {
try { try {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get() val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG)) return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG))
}catch ( e : ExecutionException) {} } catch ( e : ExecutionException) {
e.sendSilentlyWithAcra()
}
} }
else if (url.lowercase(Locale.US).contains(".png")) { else if (url.lowercase(Locale.US).contains(".png")) {
try { try {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get() val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG)) return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG))
}catch ( e : ExecutionException) {} } catch ( e : ExecutionException) {
e.sendSilentlyWithAcra()
}
} }
else if (url.lowercase(Locale.US).contains(".webp")) { else if (url.lowercase(Locale.US).contains(".webp")) {
try { try {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get() val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP)) return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP))
}catch ( e : ExecutionException) {} } catch ( e : ExecutionException) {
e.sendSilentlyWithAcra()
}
} }
return super.shouldInterceptRequest(view, url) return super.shouldInterceptRequest(view, url)
@ -395,6 +405,7 @@ class ArticleFragment : Fragment(), DIAware {
val itemUrl = URL(url) val itemUrl = URL(url)
baseUrl = itemUrl.protocol + "://" + itemUrl.host baseUrl = itemUrl.protocol + "://" + itemUrl.host
} catch (e: MalformedURLException) { } catch (e: MalformedURLException) {
e.sendSilentlyWithAcra()
} }
val fontName = when (font) { val fontName = when (font) {

View File

@ -7,6 +7,7 @@ import bou.amine.apps.readerforselfossv2.utils.getImages
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import org.acra.ktx.sendSilentlyWithAcra
fun SelfossModel.Item.preloadImages(context: Context) : Boolean { fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
val imageUrls = this.getImages() val imageUrls = this.getImages()
@ -23,6 +24,7 @@ fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
} }
} }
} catch (e : Error) { } catch (e : Error) {
e.sendSilentlyWithAcra()
return false return false
} }
@ -35,7 +37,7 @@ fun String.toTextDrawableString(): String {
try { try {
textDrawable.append(s[0]) textDrawable.append(s[0])
} catch (e: StringIndexOutOfBoundsException) { } catch (e: StringIndexOutOfBoundsException) {
// We do nothing e.sendSilentlyWithAcra()
} }
} }
return textDrawable.toString() return textDrawable.toString()

View File

@ -17,6 +17,8 @@ import androidx.preference.PreferenceFragmentCompat
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBinding import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBinding
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import org.acra.ktx.sendSilentlyWithAcra
import org.acra.ktx.sendWithAcra
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.closestDI import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
@ -101,6 +103,11 @@ class SettingsActivity : AppCompatActivity(),
class MainPreferenceFragment : PreferenceFragmentCompat() { class MainPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_main, rootKey) setPreferencesFromResource(R.xml.pref_main, rootKey)
preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
true
}
} }
} }
@ -117,6 +124,7 @@ class SettingsActivity : AppCompatActivity(),
val input: Int = (dest.toString() + source.toString()).toInt() val input: Int = (dest.toString() + source.toString()).toInt()
if (input in 1..200) return@InputFilter null if (input in 1..200) return@InputFilter null
} catch (nfe: NumberFormatException) { } catch (nfe: NumberFormatException) {
nfe.sendSilentlyWithAcra()
Toast.makeText(activity, R.string.items_number_should_be_number, Toast.LENGTH_LONG).show() Toast.makeText(activity, R.string.items_number_should_be_number, Toast.LENGTH_LONG).show()
} }
"" ""
@ -140,6 +148,7 @@ class SettingsActivity : AppCompatActivity(),
try { try {
editText.textSize = editable.toString().toInt().toFloat() editText.textSize = editable.toString().toInt().toFloat()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
e.sendSilentlyWithAcra()
} }
} }
} } } }
@ -149,6 +158,7 @@ class SettingsActivity : AppCompatActivity(),
val input = (dest.toString() + source.toString()).toInt() val input = (dest.toString() + source.toString()).toInt()
if (input > 0) return@InputFilter null if (input > 0) return@InputFilter null
} catch (nfe: NumberFormatException) { } catch (nfe: NumberFormatException) {
nfe.sendSilentlyWithAcra()
} }
"" ""
} }

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
</vector>

View File

@ -0,0 +1,7 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M21,8c-1.45,0 -2.26,1.44 -1.93,2.51l-3.55,3.56c-0.3,-0.09 -0.74,-0.09 -1.04,0l-2.55,-2.55C12.27,10.45 11.46,9 10,9c-1.45,0 -2.27,1.44 -1.93,2.52l-4.56,4.55C2.44,15.74 1,16.55 1,18c0,1.1 0.9,2 2,2c1.45,0 2.26,-1.44 1.93,-2.51l4.55,-4.56c0.3,0.09 0.74,0.09 1.04,0l2.55,2.55C12.73,16.55 13.54,18 15,18c1.45,0 2.27,-1.44 1.93,-2.52l3.56,-3.55C21.56,12.26 23,11.45 23,10C23,8.9 22.1,8 21,8z"/>
<path android:fillColor="@android:color/white" android:pathData="M15,9l0.94,-2.07l2.06,-0.93l-2.06,-0.93l-0.94,-2.07l-0.92,2.07l-2.08,0.93l2.08,0.93z"/>
<path android:fillColor="@android:color/white" android:pathData="M3.5,11l0.5,-2l2,-0.5l-2,-0.5l-0.5,-2l-0.5,2l-2,0.5l2,0.5z"/>
</vector>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Thème clair</string> <string name="mode_light">Thème clair</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -1,4 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">浅色模式</string> <string name="mode_light">浅色模式</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -134,4 +134,8 @@
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="drawer_error_loading_sources">Error loading sources…</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -137,4 +137,8 @@
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="pref_switch_enable_analytics">Enable analytics</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page (Settings > Debug). Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
</resources> </resources>

View File

@ -1,12 +1,6 @@
<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">
<SwitchPreference
android:defaultValue="true"
app:iconSpaceReserved="false"
android:key="enable_analytics"
android:title="@string/pref_switch_enable_analytics" />
<EditTextPreference <EditTextPreference
android:inputType="number" android:inputType="number"
android:key="api_timeout" android:key="api_timeout"

View File

@ -1,5 +1,6 @@
<?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"
android:title="@string/title_activity_settings"> android:title="@string/title_activity_settings">
<Preference <Preference
@ -17,9 +18,13 @@
android:title="@string/pref_header_offline" android:title="@string/pref_header_offline"
android:icon="@drawable/ic_signal_wifi_off_black_24dp" /> android:icon="@drawable/ic_signal_wifi_off_black_24dp" />
<Preference <ListPreference
android:fragment="bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity$ThemePreferenceFragment" android:defaultValue="0"
android:entries="@array/ModeTitles"
android:entryValues="@array/ModeValues"
android:key="currentMode"
android:title="@string/pref_header_theme" android:title="@string/pref_header_theme"
app:useSimpleSummaryProvider="false"
android:icon="@drawable/ic_color_lens_black_24dp" /> android:icon="@drawable/ic_color_lens_black_24dp" />
<Preference <Preference
@ -32,4 +37,18 @@
android:title="@string/pref_header_experimental" android:title="@string/pref_header_experimental"
android:icon="@drawable/ic_widgets_black_24dp" /> android:icon="@drawable/ic_widgets_black_24dp" />
<SwitchPreference
android:defaultValue="false"
android:key="enable_analytics"
android:title="@string/pref_switch_enable_analytics"
android:icon="@drawable/ic_baseline_insights_24"/>
<SwitchPreference
android:defaultValue="false"
android:key="acra.disable"
android:title="@string/pref_switch_disable_acra"
android:icon="@drawable/ic_baseline_bug_report_24"/>
</PreferenceScreen> </PreferenceScreen>

View File

@ -1023,7 +1023,7 @@ class RepositoryTest {
@Test @Test
fun create_source() { fun create_source() {
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns
SuccessResponse(true) SuccessResponse(true)
initializeRepository() initializeRepository()
@ -1045,7 +1045,6 @@ class RepositoryTest {
any(), any(),
any(), any(),
any(), any(),
any()
) )
} }
assertSame(true, response) assertSame(true, response)
@ -1053,7 +1052,7 @@ class RepositoryTest {
@Test @Test
fun create_source_but_response_fails() { fun create_source_but_response_fails() {
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns
SuccessResponse(false) SuccessResponse(false)
initializeRepository() initializeRepository()
@ -1075,7 +1074,6 @@ class RepositoryTest {
any(), any(),
any(), any(),
any(), any(),
any()
) )
} }
assertSame(false, response) assertSame(false, response)
@ -1083,7 +1081,7 @@ class RepositoryTest {
@Test @Test
fun create_source_without_connection() { fun create_source_without_connection() {
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any(), any()) } returns coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns
SuccessResponse(true) SuccessResponse(true)
initializeRepository(MutableStateFlow(false)) initializeRepository(MutableStateFlow(false))
@ -1104,7 +1102,6 @@ class RepositoryTest {
any(), any(),
any(), any(),
any(), any(),
any(),
any() any()
) )
} }

View File

@ -23,6 +23,14 @@ class StatusAndData<T>(val success: Boolean, val data: T? = null) {
} }
} }
suspend fun responseOrSuccessIf404(r: HttpResponse): SuccessResponse {
return if (r.status === HttpStatusCode.NotFound) {
SuccessResponse(true)
} else {
maybeResponse(r)
}
}
suspend fun maybeResponse(r: HttpResponse): SuccessResponse { suspend fun maybeResponse(r: HttpResponse): SuccessResponse {
return if (r.status.isSuccess()) { return if (r.status.isSuccess()) {
r.body() r.body()

View File

@ -340,8 +340,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
url, url,
spout, spout,
tags, tags,
filter, filter
appSettingsService.getApiVersion()
).isSuccess == true ).isSuccess == true
} }
@ -373,12 +372,29 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
val response = api.login() val response = api.login()
result = response.isSuccess == true result = response.isSuccess == true
} catch (cause: Throwable) { } catch (cause: Throwable) {
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.updateRemote") Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.login")
} }
} }
return result return result
} }
suspend fun logout() {
if (isNetworkAvailable()) {
try {
val response = api.logout()
if (response.isSuccess) {
Napier.e("Couldn't logout.", tag = "RepositoryImpl.logout")
}
} catch (cause: Throwable) {
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.logout")
} finally {
appSettingsService.clearAll()
}
} else {
appSettingsService.clearAll()
}
}
fun refreshLoginInformation(url: String, login: String, password: String) { fun refreshLoginInformation(url: String, login: String, password: String) {
appSettingsService.refreshLoginInformation(url, login, password) appSettingsService.refreshLoginInformation(url, login, password)
baseUrl = url baseUrl = url

View File

@ -2,11 +2,12 @@ package bou.amine.apps.readerforselfossv2.rest
import bou.amine.apps.readerforselfossv2.model.* import bou.amine.apps.readerforselfossv2.model.*
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import io.github.aakira.napier.Napier
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.* import io.ktor.client.plugins.*
import io.ktor.client.plugins.cache.* import io.ktor.client.plugins.cache.*
import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.cookies.*
import io.ktor.client.plugins.logging.* import io.ktor.client.plugins.logging.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.request.forms.* import io.ktor.client.request.forms.*
@ -20,7 +21,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
var client = createHttpClient() var client = createHttpClient()
private fun createHttpClient(): HttpClient { private fun createHttpClient(): HttpClient {
return HttpClient { val client = HttpClient {
install(ContentNegotiation) { install(ContentNegotiation) {
install(HttpCache) install(HttpCache)
json(Json { json(Json {
@ -32,7 +33,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
install(Logging) { install(Logging) {
logger = object : Logger { logger = object : Logger {
override fun log(message: String) { override fun log(message: String) {
appSettingsService.logApiCalls(message) Napier.d(message, tag = "LogApiCalls")
} }
} }
level = LogLevel.INFO level = LogLevel.INFO
@ -40,22 +41,26 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
install(HttpTimeout) { install(HttpTimeout) {
requestTimeoutMillis = appSettingsService.getApiTimeout() requestTimeoutMillis = appSettingsService.getApiTimeout()
} }
/* TODO: Auth as basic install(HttpCookies)
if (apiDetailsService.getUserName().isNotEmpty() && apiDetailsService.getPassword().isNotEmpty()) {
install(Auth) {
basic {
credentials {
BasicAuthCredentials(username = apiDetailsService.getUserName(), password = apiDetailsService.getPassword())
}
sendWithoutRequest {
true
}
}
}
}*/
expectSuccess = false expectSuccess = false
} }
client.plugin(HttpSend).intercept { request ->
val originalCall = execute(request)
if (originalCall.response.status == HttpStatusCode.Forbidden && shouldHavePostLogin() && hasLoginInfo()) {
Napier.i("Forbidden action, will try to login and retry", tag = "HttpSend")
if (login().isSuccess) {
Napier.i("Logged in worked", tag = "HttpSend")
execute(request)
}
originalCall
} else {
originalCall
}
}
return client
} }
fun url(path: String) = fun url(path: String) =
@ -66,11 +71,38 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
client = createHttpClient() client = createHttpClient()
} }
// Api version was introduces after the POST login, so when there is a version, it should be available
private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1
private fun hasLoginInfo() = appSettingsService.getUserName() != null && appSettingsService.getPassword() != null
suspend fun login(): SuccessResponse = suspend fun login(): SuccessResponse =
maybeResponse(client.get(url("/login")) { if (shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) postLogin()
parameter("password", appSettingsService.getPassword()) } else {
}) getLogin()
}
private suspend fun getLogin() = maybeResponse(client.get(url("/login")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
})
private suspend fun postLogin() = maybeResponse(client.post(url("/login")) {
parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
})
private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
suspend fun logout(): SuccessResponse =
if (shouldHaveNewLogout()) {
doLogout()
} else {
maybeLogoutIfAvailable()
}
private suspend fun maybeLogoutIfAvailable() = responseOrSuccessIf404(client.get(url("/logout")))
private suspend fun doLogout() = maybeResponse(client.delete(url("/api/session/current")))
suspend fun getItems( suspend fun getItems(
type: String, type: String,
@ -82,80 +114,102 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
items: Int? = null items: Int? = null
): StatusAndData<List<SelfossModel.Item>> = ): StatusAndData<List<SelfossModel.Item>> =
bodyOrFailure(client.get(url("/items")) { bodyOrFailure(client.get(url("/items")) {
if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
parameter("type", type) }
parameter("tag", tag) parameter("type", type)
parameter("source", source) parameter("tag", tag)
parameter("search", search) parameter("source", source)
parameter("updatedsince", updatedSince) parameter("search", search)
parameter("items", items ?: appSettingsService.getItemsNumber()) parameter("updatedsince", updatedSince)
parameter("offset", offset) parameter("items", items ?: appSettingsService.getItemsNumber())
}) parameter("offset", offset)
})
suspend fun stats(): StatusAndData<SelfossModel.Stats> = suspend fun stats(): StatusAndData<SelfossModel.Stats> =
bodyOrFailure(client.get(url("/stats")) { bodyOrFailure(client.get(url("/stats")) {
if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
}) }
})
suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> = suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> =
bodyOrFailure(client.get(url("/tags")) { bodyOrFailure(client.get(url("/tags")) {
if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
}) }
})
suspend fun update(): StatusAndData<String> = suspend fun update(): StatusAndData<String> =
bodyOrFailure(client.get(url("/update")) { bodyOrFailure(client.get(url("/update")) {
if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
}) }
})
suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> = suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> =
bodyOrFailure(client.get(url("/sources/spouts")) { bodyOrFailure(client.get(url("/sources/spouts")) {
if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
}) }
})
suspend fun sources(): StatusAndData<ArrayList<SelfossModel.Source>> = suspend fun sources(): StatusAndData<ArrayList<SelfossModel.Source>> =
bodyOrFailure(client.get(url("/sources/list")) { bodyOrFailure(client.get(url("/sources/list")) {
if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
}) }
})
suspend fun version(): StatusAndData<SelfossModel.ApiVersion> = suspend fun version(): StatusAndData<SelfossModel.ApiVersion> =
bodyOrFailure(client.get(url("/api/about"))) bodyOrFailure(client.get(url("/api/about")))
suspend fun markAsRead(id: String): SuccessResponse = suspend fun markAsRead(id: String): SuccessResponse =
maybeResponse(client.post(url("/mark/$id")) { maybeResponse(client.post(url("/mark/$id")) {
parameter("username", appSettingsService.getUserName()) if (!shouldHavePostLogin()) {
parameter("password", appSettingsService.getPassword()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
}) })
suspend fun unmarkAsRead(id: String): SuccessResponse = suspend fun unmarkAsRead(id: String): SuccessResponse =
maybeResponse(client.post(url("/unmark/$id")) { maybeResponse(client.post(url("/unmark/$id")) {
parameter("username", appSettingsService.getUserName()) if (!shouldHavePostLogin()) {
parameter("password", appSettingsService.getPassword()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
}) })
suspend fun starr(id: String): SuccessResponse = suspend fun starr(id: String): SuccessResponse =
maybeResponse(client.post(url("/starr/$id")) { maybeResponse(client.post(url("/starr/$id")) {
parameter("username", appSettingsService.getUserName()) if (!shouldHavePostLogin()) {
parameter("password", appSettingsService.getPassword()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
}) })
suspend fun unstarr(id: String): SuccessResponse = suspend fun unstarr(id: String): SuccessResponse =
maybeResponse(client.post(url("/unstarr/$id")) { maybeResponse(client.post(url("/unstarr/$id")) {
parameter("username", appSettingsService.getUserName()) if (!shouldHavePostLogin()) {
parameter("password", appSettingsService.getPassword()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword())
}
}) })
suspend fun markAllAsRead(ids: List<String>): SuccessResponse = suspend fun markAllAsRead(ids: List<String>): SuccessResponse =
maybeResponse(client.submitForm( maybeResponse(client.submitForm(
url = url("/mark"), url = url("/mark"),
formParameters = Parameters.build { formParameters = Parameters.build {
append("username", appSettingsService.getUserName()) if (!shouldHavePostLogin()) {
append("password", appSettingsService.getPassword()) append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
}
ids.map { append("ids[]", it) } ids.map { append("ids[]", it) }
} }
)) ))
@ -165,18 +219,17 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String,
filter: String, filter: String
version: Int
): SuccessResponse = ): SuccessResponse =
maybeResponse( maybeResponse(
if (version > 1) { if (appSettingsService.getApiVersion() > 1) {
createSource2(title, url, spout, tags, filter) createSource2(title, url, spout, tags, filter)
} else { } else {
createSource(title, url, spout, tags, filter) createSource(title, url, spout, tags, filter)
} }
) )
suspend fun createSource( private suspend fun createSource(
title: String, title: String,
url: String, url: String,
spout: String, spout: String,
@ -184,8 +237,13 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
filter: String filter: String
): HttpResponse = ): HttpResponse =
client.submitForm( client.submitForm(
url = url("/source?username=${appSettingsService.getUserName()}&password=${appSettingsService.getPassword()}"), url = url("/source"),
formParameters = Parameters.build { formParameters = Parameters.build {
// TODO: test this
if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
}
append("title", title) append("title", title)
append("url", url) append("url", url)
append("spout", spout) append("spout", spout)
@ -194,7 +252,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
} }
) )
suspend fun createSource2( private suspend fun createSource2(
title: String, title: String,
url: String, url: String,
spout: String, spout: String,
@ -202,8 +260,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
filter: String filter: String
): HttpResponse = ): HttpResponse =
client.submitForm( client.submitForm(
url = url("/source?username=${appSettingsService.getUserName()}&password=${appSettingsService.getPassword()}"), url = url("/source"),
formParameters = Parameters.build { formParameters = Parameters.build {
if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
}
append("title", title) append("title", title)
append("url", url) append("url", url)
append("spout", spout) append("spout", spout)
@ -214,7 +276,9 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
suspend fun deleteSource(id: Int): SuccessResponse = suspend fun deleteSource(id: Int): SuccessResponse =
maybeResponse(client.delete(url("/source/$id")) { maybeResponse(client.delete(url("/source/$id")) {
if (!shouldHavePostLogin()) {
parameter("username", appSettingsService.getUserName()) parameter("username", appSettingsService.getUserName())
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
}) }
})
} }

View File

@ -44,10 +44,6 @@ class AppSettingsService {
refreshUserSettings() refreshUserSettings()
} }
fun logApiCalls(message: String) {
Napier.d(message, tag = "LogApiCalls")
}
fun getApiVersion(): Int { fun getApiVersion(): Int {
if (_apiVersion == -1) { if (_apiVersion == -1) {
refreshApiVersion() refreshApiVersion()