Compare commits

..

2 Commits

Author SHA1 Message Date
4fbebf2954 chore: code style fixes for ktlint
Some checks failed
Check PR code / Lint (pull_request) Failing after 2m19s
Check PR code / build (pull_request) Has been skipped
2025-01-11 15:23:02 +01:00
5035392aff chore: more debug to fix a context issue. 2025-01-11 12:52:55 +01:00
74 changed files with 556 additions and 1414 deletions

View File

@@ -16,13 +16,13 @@ jobs:
java-version: '17'
cache: gradle
- name: Install klint
run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.5.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.0.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
- name: Install detekt
run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.7/detekt-cli-1.23.7.zip && unzip detekt-cli-1.23.7.zip
run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.1/detekt-cli-1.23.1.zip && unzip detekt-cli-1.23.1.zip
- name: Linting...
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build'
- name: Detecting...
run: ./detekt-cli-1.23.7/bin/detekt-cli -c detekt.yml --excludes '**/shared/build/**/*.kt'
run: ./detekt-cli-1.23.1/bin/detekt-cli --all-rules --excludes '**/shared/build/**/*.kt' || true
build:
needs: Lint
uses: ./.gitea/workflows/common_build.yml

View File

@@ -1,38 +1,3 @@
**v125010241
- Merge pull request 'fix: Link not opening.' (#178) from fix-open-link into master
- refactor: context fragments issues.
- logs: Context issues.
- fix: Handle empty url issue, again.
- fix: Link not opening.
- Changelog for v125010201
--------------------------------------------------------------------
**v125010201
- fix: Handle empty url issue.
- Merge pull request 'Removed the floating bar.' (#177) from floating-bar into master
- chore: changing actions in reader fragment.
- Changelog for v125010131
--------------------------------------------------------------------
**v125010131
- fix: reload the adapter when it's needed. Fixes #128. (#176)
- feat: basic auth and images loading. Fixes #172. (#175)
- Changelog for v125010111
--------------------------------------------------------------------
**v125010111
- Debug trying to fix context issues. (#174)
- Changelog for v125010031
--------------------------------------------------------------------
**v125010031
- Merge pull request 'Bump dependencies' (#173) from upgarde into master

View File

@@ -156,7 +156,7 @@ dependencies {
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
// Themes
implementation("com.leinardi.android:speed-dial:3.3.0")
implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
// Pager
implementation("me.relex:circleindicator:2.1.6")

View File

@@ -30,16 +30,21 @@ fun withError(
): TypeSafeMatcher<View?> {
return object : TypeSafeMatcher<View?>() {
override fun matchesSafely(view: View?): Boolean {
if (view != null && (view !is EditText || view.error == null)) {
if (view == null) {
return false
}
val context = view.context
if (view !is EditText) {
return false
}
if (view.error == null) {
return false
}
val context = view!!.context
return (view as EditText).error.toString() == context.getString(id)
return view.error.toString() == context.getString(id)
}
override fun describeTo(description: Description?) {
// Nothing
}
}
}
@@ -53,7 +58,6 @@ fun withDrawable(
description.appendText("ImageView with drawable same as drawable with id $id")
}
@Suppress("detekt:SwallowedException")
override fun matchesSafely(view: View): Boolean {
val context = view.context
val expectedBitmap = context.getDrawable(id)!!.toBitmap()

View File

@@ -1,5 +1,6 @@
package bou.amine.apps.readerforselfossv2.android
import android.app.Activity
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions.click
@@ -25,6 +26,14 @@ class LoginActivityTest {
@get:Rule
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
private fun getActivity(): Activity? {
var activity: Activity? = null
activityRule.scenario.onActivity {
activity = it
}
return activity
}
@Before
fun registerIdlingResource() {
IdlingRegistry

View File

@@ -42,7 +42,6 @@ class SettingsActivityGeneralTest {
onView(withText(R.string.pref_header_general)).perform(click())
}
@Suppress("detekt:LongMethod")
@Test
fun testGeneral() {
onView(withText(R.string.pref_api_items_number_title)).check(matches(isDisplayed()))
@@ -65,6 +64,19 @@ class SettingsActivityGeneralTest {
),
),
)
onView(withSettingsCheckboxWidget(R.string.reader_static_bar_title)).check(
matches(
allOf(
isDisplayed(),
not(isChecked()),
),
),
)
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
matches(
isEnabled(),
),
)
onView(withText(R.string.pref_general_category_displaying)).check(matches(isDisplayed()))
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).check(
matches(
@@ -106,7 +118,6 @@ class SettingsActivityGeneralTest {
)
}
@Suppress("detekt:ForbiddenComment")
@Test
fun testGeneralActionsNumberItems() {
onView(withText(R.string.pref_api_items_number_title)).perform(click())
@@ -148,6 +159,19 @@ class SettingsActivityGeneralTest {
@Test
fun testGeneralActionsCheckboxes() {
// article viewer settings
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
matches(
isEnabled(),
),
)
onView(withSettingsCheckboxWidget(R.string.pref_article_viewer_title)).perform(click())
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
matches(
not(isEnabled()),
),
)
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(not(isEnabled())))
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).perform(click())
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(isEnabled()))

View File

@@ -42,7 +42,6 @@ class SettingsActivityOfflineTest {
onView(withText(R.string.pref_header_offline)).perform(click())
}
@Suppress("detekt:LongMethod")
@Test
fun testOffline() {
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).check(
@@ -108,7 +107,6 @@ class SettingsActivityOfflineTest {
)
}
@Suppress("detekt:LongMethod")
@Test
fun testOfflineActions() {
onView(withText(R.string.pref_switch_items_caching_off)).check(matches(isDisplayed()))

View File

@@ -45,7 +45,6 @@ class SourcesActivityTest {
)
}
@Suppress("detekt:SwallowedException")
@Test
fun addSourceCheckContent() {
testAddSourceWithUrl("https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en", sourceName)

View File

@@ -31,7 +31,7 @@ import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowserAsNewTask
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
@@ -49,8 +49,6 @@ import org.kodein.di.instance
import java.security.MessageDigest
import java.util.concurrent.TimeUnit
private const val MIN_WIDTH_CARD_DP = 300
class HomeActivity :
AppCompatActivity(),
SearchView.OnQueryTextListener,
@@ -202,7 +200,6 @@ class HomeActivity :
}
}
@Suppress("detekt:LongMethod")
private fun handleBottomBar() {
tabNewBadge =
TextBadgeItem()
@@ -285,7 +282,7 @@ class HomeActivity :
handleBottomBarActions()
handleGdprDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
handleRecurringTask()
CountingIdlingResourceSingleton.increment()
@@ -297,10 +294,10 @@ class HomeActivity :
getElementsAccordingToTab()
}
private fun handleGdprDialog(gdprShown: Boolean) {
private fun handleGDPRDialog(GDPRShown: Boolean) {
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
if (!gdprShown) {
if (!GDPRShown) {
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
@@ -317,44 +314,50 @@ class HomeActivity :
private fun reloadLayoutManager() {
val currentManager = binding.recyclerView.layoutManager
val layoutManager: RecyclerView.LayoutManager
fun gridLayoutManager() {
val layoutManager =
GridLayoutManager(
this,
calculateNoOfColumns(),
)
binding.recyclerView.layoutManager = layoutManager
}
fun staggererdGridLayoutManager() {
var layoutManager =
StaggeredGridLayoutManager(
calculateNoOfColumns(),
StaggeredGridLayoutManager.VERTICAL,
)
layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
binding.recyclerView.layoutManager = layoutManager
}
// This will only update the layout manager if settings changed
when (currentManager) {
is StaggeredGridLayoutManager ->
if (!appSettingsService.isCardViewEnabled()) {
gridLayoutManager()
layoutManager =
GridLayoutManager(
this,
calculateNoOfColumns(),
)
binding.recyclerView.layoutManager = layoutManager
}
is GridLayoutManager ->
if (appSettingsService.isCardViewEnabled()) {
staggererdGridLayoutManager()
layoutManager =
StaggeredGridLayoutManager(
calculateNoOfColumns(),
StaggeredGridLayoutManager.VERTICAL,
)
layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
binding.recyclerView.layoutManager = layoutManager
}
else ->
if (currentManager == null) {
if (!appSettingsService.isCardViewEnabled()) {
gridLayoutManager()
layoutManager =
GridLayoutManager(
this,
calculateNoOfColumns(),
)
binding.recyclerView.layoutManager = layoutManager
} else {
staggererdGridLayoutManager()
layoutManager =
StaggeredGridLayoutManager(
calculateNoOfColumns(),
StaggeredGridLayoutManager.VERTICAL,
)
layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
binding.recyclerView.layoutManager = layoutManager
}
}
}
@@ -479,8 +482,8 @@ class HomeActivity :
}
private fun handleListResult(appendResults: Boolean = false) {
val oldManager = binding.recyclerView.layoutManager
if (appendResults) {
val oldManager = binding.recyclerView.layoutManager
firstVisible =
when (oldManager) {
is StaggeredGridLayoutManager ->
@@ -493,13 +496,7 @@ class HomeActivity :
}
}
@Suppress("detekt:ComplexCondition")
if (recyclerAdapter == null ||
(
(recyclerAdapter is ItemListAdapter && appSettingsService.isCardViewEnabled()) ||
(recyclerAdapter is ItemCardAdapter && !appSettingsService.isCardViewEnabled())
)
) {
if (recyclerAdapter == null) {
if (appSettingsService.isCardViewEnabled()) {
recyclerAdapter =
ItemCardAdapter(
@@ -546,7 +543,7 @@ class HomeActivity :
private fun calculateNoOfColumns(): Int {
val displayMetrics = resources.displayMetrics
val dpWidth = displayMetrics.widthPixels / displayMetrics.density
return (dpWidth / MIN_WIDTH_CARD_DP).toInt()
return (dpWidth / 300).toInt()
}
override fun onQueryTextChange(p0: String?): Boolean {
@@ -595,11 +592,10 @@ class HomeActivity :
.show()
}
@Suppress("detekt:ReturnCount", "detekt:LongMethod")
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.issue_tracker -> {
baseContext.openUrlInBrowserAsNewTask(AppSettingsService.BUG_URL)
baseContext.openUrlInBrowser(AppSettingsService.BUG_URL)
return true
}

View File

@@ -30,8 +30,6 @@ import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
private const val MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED = 3
class LoginActivity :
AppCompatActivity(),
DIAware {
@@ -219,7 +217,7 @@ class LoginActivity :
cancel = true
binding.urlView.error = getString(R.string.login_url_problem)
inValidCount++
if (inValidCount == MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED) {
if (inValidCount == 3) {
val alertDialog = AlertDialog.Builder(this).create()
alertDialog.setTitle(getString(R.string.warning_wrong_url))
alertDialog.setMessage(getString(R.string.text_wrong_url))

View File

@@ -62,7 +62,6 @@ class MyApp :
private val connectivityStatus: ConnectivityStatus by instance()
private val driverFactory: DriverFactory by instance()
@Suppress("detekt:ForbiddenComment")
// TODO: handle with the "previous" way
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)

View File

@@ -53,7 +53,6 @@ class ReaderActivity :
showMenuItem(false)
}
@Suppress("detekt:SwallowedException")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityReaderBinding.inflate(layoutInflater)

View File

@@ -85,7 +85,6 @@ class UpsertSourceActivity :
}
}
@Suppress("detekt:SwallowedException")
private fun handleSpoutsSpinner() {
val spoutsKV = HashMap<String, String>()
binding.spoutsSpinner.onItemSelectedListener =
@@ -174,7 +173,6 @@ class UpsertSourceActivity :
sourceDetailsUnavailable -> {
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
}
else -> {
CoroutineScope(Dispatchers.Main).launch {
val successfullyAddedSource =

View File

@@ -118,13 +118,13 @@ class ItemCardAdapter(
binding.itemImage.setImageDrawable(null)
} else {
binding.itemImage.visibility = View.VISIBLE
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage, appSettingsService)
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage)
}
if (itm.getIcon(repository.baseUrl).isEmpty()) {
binding.sourceImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
} else {
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage, appSettingsService)
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage)
}
}
}

View File

@@ -65,10 +65,10 @@ class ItemListAdapter(
if (itm.getIcon(repository.baseUrl).isEmpty()) {
binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
} else {
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage, appSettingsService)
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
}
} else {
c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage, appSettingsService)
c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage)
}
}
}

View File

@@ -16,7 +16,6 @@ import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBindi
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon
import kotlinx.coroutines.CoroutineScope
@@ -37,7 +36,6 @@ class SourcesListAdapter(
override val di: DI by closestDI(app)
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
override fun onCreateViewHolder(
parent: ViewGroup,
@@ -84,7 +82,7 @@ class SourcesListAdapter(
if (itm.getIcon(repository.baseUrl).isEmpty()) {
binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded())
} else {
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage, appSettingsService)
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
}
if (!itm.error.isNullOrBlank()) {

View File

@@ -26,8 +26,6 @@ import org.kodein.di.instance
import java.util.Timer
import kotlin.concurrent.schedule
private const val NOTIFICATION_DELAY = 4000L
class LoadingWorker(
val context: Context,
params: WorkerParameters,
@@ -63,7 +61,7 @@ class LoadingWorker(
handleNewItemsNotification(apiItems, notificationManager)
}
}
apiItems.map { it.preloadImages(context, appSettingsService) }
apiItems.map { it.preloadImages(context) }
}
}
return Result.success()
@@ -108,11 +106,11 @@ class LoadingWorker(
.setAutoCancel(true)
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
Timer("", false).schedule(NOTIFICATION_DELAY) {
Timer("", false).schedule(4000) {
notificationManager.notify(2, newItemsNotification.build())
}
}
Timer("", false).schedule(NOTIFICATION_DELAY) {
Timer("", false).schedule(4000) {
notificationManager.cancel(1)
}
}

View File

@@ -2,14 +2,18 @@ package bou.amine.apps.readerforselfossv2.android.fragments
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.TypedArray
import android.graphics.Bitmap
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.util.TypedValue
import android.util.TypedValue.DATA_NULL_UNDEFINED
import android.view.GestureDetector
import android.view.InflateException
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@@ -19,6 +23,7 @@ import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import bou.amine.apps.readerforselfossv2.android.ImageActivity
import bou.amine.apps.readerforselfossv2.android.R
@@ -27,15 +32,10 @@ import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem
import bou.amine.apps.readerforselfossv2.android.model.toModel
import bou.amine.apps.readerforselfossv2.android.model.toParcelable
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.addHomeMadeActionItem
import bou.amine.apps.readerforselfossv2.android.utils.getColorFromAttr
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapFitCenter
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
import bou.amine.apps.readerforselfossv2.android.utils.glide.getGlideImageForResource
import bou.amine.apps.readerforselfossv2.android.utils.isUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.maybeIfContext
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrlInBrowserAsNewTask
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowserAsNewTask
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
import bou.amine.apps.readerforselfossv2.model.MercuryModel
import bou.amine.apps.readerforselfossv2.model.SelfossModel
@@ -46,7 +46,11 @@ import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getImages
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
import com.leinardi.android.speeddial.SpeedDialView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -61,19 +65,11 @@ import java.util.Locale
import java.util.concurrent.ExecutionException
private const val IMAGE_JPG = "image/jpg"
private const val IMAGE_PNG = "image/png"
private const val IMAGE_WEBP = "image/webp"
private const val WHITE_COLOR_HEX = 0xFFFFFF
private const val DEFAULT_FONT_SIZE = 16
class ArticleFragment :
Fragment(),
DIAware {
private var colorOnSurface: Int = 0
private var colorSurface: Int = 0
private var fontSize: Int = DEFAULT_FONT_SIZE
private var fontSize: Int = 16
private lateinit var item: SelfossModel.Item
private lateinit var url: String
private lateinit var contentText: String
@@ -81,7 +77,7 @@ class ArticleFragment :
private lateinit var contentImage: String
private lateinit var contentTitle: String
private lateinit var allImages: ArrayList<String>
private lateinit var fab: SpeedDialView
private lateinit var fab: FloatingActionButton
private lateinit var textAlignment: String
private lateinit var binding: FragmentArticleBinding
@@ -92,6 +88,7 @@ class ArticleFragment :
private var typeface: Typeface? = null
private var resId: Int = 0
private var font = ""
private var staticBar = false
private val mercuryApi: MercuryApi by instance()
@@ -103,7 +100,6 @@ class ArticleFragment :
item = pi.toModel()
}
@Suppress("detekt:LongMethod")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -118,9 +114,6 @@ class ArticleFragment :
e.sendSilentlyWithAcra()
}
colorOnSurface = getColorFromAttr(R.attr.colorOnSurface)
colorSurface = getColorFromAttr(R.attr.colorSurface)
contentText = item.content
contentTitle = item.title.getHtmlDecoded()
contentImage = item.getThumbnail(repository.baseUrl)
@@ -134,11 +127,23 @@ class ArticleFragment :
allImages = item.getImages()
fontSize = appSettingsService.getFontSize()
staticBar = appSettingsService.isStaticBarEnabled()
font = appSettingsService.getFont()
refreshAlignment()
handleFloatingToolbar()
fab = binding.fab
fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent))
fab.rippleColor = resources.getColor(R.color.colorAccentDark)
val floatingToolbar: FloatingToolbar = handleFloatingToolbar()
if (staticBar) {
fab.hide()
floatingToolbar.show()
}
binding.source.text = contentSource
if (typeface != null) {
@@ -146,13 +151,28 @@ class ArticleFragment :
}
handleContent()
binding.nestedScrollView.setOnScrollChangeListener(
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) {
floatingToolbar.hide()
fab.hide()
} else {
if (staticBar) {
floatingToolbar.show()
} else {
if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
}
}
},
)
} catch (e: InflateException) {
e.sendSilentlyWithAcraWithName("webview not available")
maybeIfContext {
try {
AlertDialog
.Builder(it)
.setMessage(it.getString(R.string.webview_dialog_issue_message))
.setTitle(it.getString(R.string.webview_dialog_issue_title))
.Builder(requireContext())
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
.setPositiveButton(
android.R.string.ok,
) { _, _ ->
@@ -160,6 +180,8 @@ class ArticleFragment :
requireActivity().finish()
}.create()
.show()
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context required is null")
}
}
@@ -181,84 +203,72 @@ class ArticleFragment :
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
binding.imageView.visibility = View.VISIBLE
maybeIfContext { it.bitmapFitCenter(contentImage, binding.imageView, appSettingsService) }
Glide
.with(requireContext())
.asBitmap()
.load(contentImage)
.apply(RequestOptions.fitCenterTransform())
.into(binding.imageView)
} else {
binding.imageView.visibility = View.GONE
}
}
}
private fun handleFloatingToolbar() {
fab = binding.speedDial
fab.mainFabClosedIconColor = colorOnSurface
fab.mainFabOpenedIconColor = colorOnSurface
maybeIfContext { handleFloatingToolbarActionItems(it) }
fab.setOnActionSelectedListener { actionItem ->
when (actionItem.id) {
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
R.id.open_action -> requireActivity().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
R.id.unread_action ->
if (this@ArticleFragment.item.unread) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = false
maybeIfContext {
Toast
.makeText(
it,
R.string.marked_as_read,
Toast.LENGTH_LONG,
).show()
}
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = true
maybeIfContext {
Toast
.makeText(
it,
R.string.marked_as_unread,
Toast.LENGTH_LONG,
).show()
}
}
else -> Unit
}
false
private fun handleFloatingToolbar(): FloatingToolbar {
val floatingToolbar: FloatingToolbar = binding.floatingToolbar
if (appSettingsService.getPublicAccess()) {
floatingToolbar.setMenu(R.menu.reader_toolbar_no_read)
}
}
floatingToolbar.attachFab(fab)
private fun handleFloatingToolbarActionItems(c: Context) {
fab.addHomeMadeActionItem(
R.id.share_action,
resources.getDrawable(R.drawable.ic_share_white_24dp),
R.string.reader_action_share,
colorOnSurface,
colorSurface,
c,
)
fab.addHomeMadeActionItem(
R.id.open_action,
resources.getDrawable(R.drawable.ic_open_in_browser_white_24dp),
R.string.reader_action_open,
colorOnSurface,
colorSurface,
c,
)
fab.addHomeMadeActionItem(
R.id.unread_action,
resources.getDrawable(R.drawable.ic_baseline_white_eye_24dp),
R.string.unmark,
colorOnSurface,
colorSurface,
c,
floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
floatingToolbar.setClickListener(
object : FloatingToolbar.ItemClickListener {
override fun onItemClick(item: MenuItem) {
when (item.itemId) {
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
R.id.open_action -> requireActivity().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
R.id.unread_action ->
try {
if (this@ArticleFragment.item.unread) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = false
Toast
.makeText(
requireContext(),
R.string.marked_as_read,
Toast.LENGTH_LONG,
).show()
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = true
Toast
.makeText(
context,
R.string.marked_as_unread,
Toast.LENGTH_LONG,
).show()
}
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context required is null")
}
else -> Unit
}
}
override fun onItemLongClick(item: MenuItem?) {
// We do nothing
}
},
)
return floatingToolbar
}
private fun refreshAlignment() {
@@ -270,7 +280,6 @@ class ArticleFragment :
}
}
@Suppress("detekt:SwallowedException")
private fun getContentFromMercury() {
binding.progressBar.visibility = View.VISIBLE
@@ -309,12 +318,16 @@ class ArticleFragment :
}
}
private fun handleLeadImage(leadImageUrl: String?) {
if (!leadImageUrl.isNullOrEmpty()) {
maybeIfContext {
binding.imageView.visibility = View.VISIBLE
it.bitmapFitCenter(leadImageUrl, binding.imageView, appSettingsService)
}
private fun handleLeadImage(lead_image_url: String?) {
if (!lead_image_url.isNullOrEmpty() && context != null) {
binding.imageView.visibility = View.VISIBLE
Glide
.with(requireContext())
.asBitmap()
.load(
lead_image_url,
).apply(RequestOptions.fitCenterTransform())
.into(binding.imageView)
} else {
binding.imageView.visibility = View.GONE
}
@@ -328,79 +341,132 @@ class ArticleFragment :
view: WebView?,
url: String,
): Boolean =
if (url.isUrlValid() &&
if (context != null &&
url.isUrlValid() &&
binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
) {
maybeIfContext { it.openUrlInBrowserAsNewTask(url) }
requireContext().openUrlInBrowser(url)
true
} else {
false
}
@Suppress("detekt:SwallowedException", "detekt:ReturnCount")
@Deprecated("Deprecated in Java")
override fun shouldInterceptRequest(
view: WebView,
url: String,
): WebResourceResponse? {
val (mime: String?, compression: Bitmap.CompressFormat) =
if (url
.lowercase(Locale.US)
.contains(".jpg") ||
url.lowercase(Locale.US).contains(".jpeg")
) {
Pair(IMAGE_JPG, Bitmap.CompressFormat.JPEG)
} else if (url.lowercase(Locale.US).contains(".png")) {
Pair(IMAGE_PNG, Bitmap.CompressFormat.PNG)
} else if (url.lowercase(Locale.US).contains(".webp")) {
Pair(IMAGE_WEBP, Bitmap.CompressFormat.WEBP)
} else {
return super.shouldInterceptRequest(view, url)
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
if (url.lowercase(Locale.US).contains(".jpg") ||
url
.lowercase(Locale.US)
.contains(".jpeg")
) {
try {
val image =
Glide
.with(view)
.asBitmap()
.apply(glideOptions)
.load(url)
.submit()
.get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG),
)
} catch (e: ExecutionException) {
// Do nothing
}
} else if (url.lowercase(Locale.US).contains(".png")) {
try {
val image =
Glide
.with(view)
.asBitmap()
.apply(glideOptions)
.load(url)
.submit()
.get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.PNG),
)
} catch (e: ExecutionException) {
// Do nothing
}
} else if (url.lowercase(Locale.US).contains(".webp")) {
try {
val image =
Glide
.with(view)
.asBitmap()
.apply(glideOptions)
.load(url)
.submit()
.get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP),
)
} catch (e: ExecutionException) {
// Do nothing
}
try {
val image = view.getGlideImageForResource(url, appSettingsService)
return WebResourceResponse(
mime,
"UTF-8",
getBitmapInputStream(image, compression),
)
} catch (e: ExecutionException) {
return super.shouldInterceptRequest(view, url)
}
return super.shouldInterceptRequest(view, url)
}
}
}
@Suppress("detekt:LongMethod", "detekt:ImplicitDefaultLocale")
private fun htmlToWebview() {
maybeIfContext {
val context: Context
try {
context = requireContext()
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context required is null")
return
}
val colorOnSurface = TypedValue()
val colorSurface = TypedValue()
try {
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
val a: TypedArray = it.obtainStyledAttributes(resId, attrs)
val a: TypedArray = context.obtainStyledAttributes(resId, attrs)
binding.webcontent.settings.standardFontFamily = a.getString(0)
""
binding.webcontent.visibility = View.VISIBLE
context.theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
context.theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context issue when setting attributes, but context wasn't null before")
}
binding.webcontent.visibility = View.VISIBLE
val colorSurfaceString =
String.format(
"#%06X",
WHITE_COLOR_HEX and (if (colorSurface != DATA_NULL_UNDEFINED) colorSurface else WHITE_COLOR_HEX),
0xFFFFFF and (if (colorSurface.data != DATA_NULL_UNDEFINED) colorSurface.data else 0xFFFFFF),
)
val colorOnSurfaceString =
String.format(
"#%06X",
WHITE_COLOR_HEX and (if (colorOnSurface != DATA_NULL_UNDEFINED) colorOnSurface else 0),
0xFFFFFF and (if (colorOnSurface.data != DATA_NULL_UNDEFINED) colorOnSurface.data else 0),
)
binding.webcontent.settings.useWideViewPort = true
binding.webcontent.settings.loadWithOverviewMode = true
binding.webcontent.settings.javaScriptEnabled = false
handleImageLoading()
try {
binding.webcontent.settings.useWideViewPort = true
binding.webcontent.settings.loadWithOverviewMode = true
binding.webcontent.settings.javaScriptEnabled = false
handleImageLoading()
val gestureDetector =
GestureDetector(
activity,
@@ -414,50 +480,49 @@ class ArticleFragment :
event,
)
}
binding.webcontent.settings.layoutAlgorithm =
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Gesture detector issue ?")
e.sendSilentlyWithAcraWithName("Context is null but wasn't, and that's causing issues with webview config")
return
}
binding.webcontent.settings.layoutAlgorithm =
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
var baseUrl: String? = null
try {
val itemUrl = URL(url)
baseUrl = itemUrl.protocol + "://" + itemUrl.host
} catch (e: MalformedURLException) {
e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
}
var baseUrl: String? = null
try {
val itemUrl = URL(url)
baseUrl = itemUrl.protocol + "://" + itemUrl.host
} catch (e: MalformedURLException) {
e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
}
val fontName: String =
maybeIfContext {
val fontName =
when (font) {
it.getString(R.string.open_sans_font_id) -> "Open Sans"
it.getString(R.string.roboto_font_id) -> "Roboto"
it.getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
getString(R.string.open_sans_font_id) -> "Open Sans"
getString(R.string.roboto_font_id) -> "Roboto"
getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
else -> ""
}
}?.toString().orEmpty()
val fontLinkAndStyle =
if (fontName.isNotEmpty()) {
"""<link href="https://fonts.googleapis.com/css?family=${
fontName.replace(
" ",
"+",
)
}" rel="stylesheet">
val fontLinkAndStyle =
if (font.isNotEmpty()) {
"""<link href="https://fonts.googleapis.com/css?family=${
fontName.replace(
" ",
"+",
)
}" rel="stylesheet">
|<style>
| * {
| font-family: '$fontName';
| }
|</style>
""".trimMargin()
} else {
""
}
try {
""".trimMargin()
} else {
""
}
binding.webcontent.loadDataWithBaseURL(
baseUrl,
"""<html>
@@ -474,7 +539,7 @@ class ArticleFragment :
| color: ${
String.format(
"#%06X",
WHITE_COLOR_HEX and (maybeIfContext { it.resources.getColor(R.color.colorAccent) } as Int),
0xFFFFFF and context.resources.getColor(R.color.colorAccent),
)
} !important;
| }
@@ -531,8 +596,10 @@ class ArticleFragment :
private fun openInBrowserAfterFailing() {
binding.progressBar.visibility = View.GONE
maybeIfContext {
it.openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
try {
requireContext().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
} catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context required is null")
}
}

View File

@@ -1,5 +1,6 @@
package bou.amine.apps.readerforselfossv2.android.fragments
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
@@ -15,13 +16,11 @@ import bou.amine.apps.readerforselfossv2.android.HomeActivity
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.android.utils.glide.imageIntoViewTarget
import bou.amine.apps.readerforselfossv2.android.utils.maybeIfContext
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.getColorHexCode
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.ViewTarget
import com.bumptech.glide.request.transition.Transition
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@@ -34,15 +33,12 @@ import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI
import org.kodein.di.instance
private const val DRAWABLE_SIZE = 30
class FilterSheetFragment :
BottomSheetDialogFragment(),
DIAware {
private lateinit var binding: FilterFragmentBinding
override val di: DI by closestDI()
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
private var selectedChip: Chip? = null
@@ -60,8 +56,8 @@ class FilterSheetFragment :
try {
CoroutineScope(Dispatchers.Main).launch {
handleTagChips()
handleSourceChips()
handleTagChips(requireContext())
handleSourceChips(requireContext())
binding.progressBar2.visibility = GONE
binding.filterView.visibility = VISIBLE
@@ -79,16 +75,17 @@ class FilterSheetFragment :
return binding.root
}
private suspend fun handleSourceChips() {
private suspend fun handleSourceChips(context: Context) {
val sourceGroup = binding.sourcesGroup
repository.getSourcesDetailsOrStats().forEachIndexed { _, source ->
val c = Chip(context)
c.ellipsize = TextUtils.TruncateAt.END
maybeIfContext {
it.imageIntoViewTarget(
source.getIcon(repository.baseUrl),
Glide
.with(context)
.load(source.getIcon(repository.baseUrl))
.into(
object : ViewTarget<Chip?, Drawable?>(c) {
override fun onResourceReady(
resource: Drawable,
@@ -101,9 +98,7 @@ class FilterSheetFragment :
}
}
},
appSettingsService,
)
}
c.text = source.title.getHtmlDecoded()
@@ -139,7 +134,7 @@ class FilterSheetFragment :
}
}
private suspend fun handleTagChips() {
private suspend fun handleTagChips(context: Context) {
val tagGroup = binding.tagsGroup
val tags = repository.getTags()
@@ -161,8 +156,8 @@ class FilterSheetFragment :
}
gd.setColor(gdColor)
gd.shape = GradientDrawable.RECTANGLE
gd.setSize(DRAWABLE_SIZE, DRAWABLE_SIZE)
gd.cornerRadius = DRAWABLE_SIZE.toFloat()
gd.setSize(30, 30)
gd.cornerRadius = 30F
c.chipIcon = gd
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")

View File

@@ -6,19 +6,13 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentImageBinding
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapWithCache
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI
import org.kodein.di.instance
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
class ImageFragment :
Fragment(),
DIAware {
override val di: DI by closestDI()
private val appSettingsService: AppSettingsService by instance()
class ImageFragment : Fragment() {
private lateinit var imageUrl: String
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
private var _binding: FragmentImageBinding? = null
val binding get() = _binding
@@ -37,7 +31,12 @@ class ImageFragment :
val view = binding?.root
binding!!.photoView.visibility = View.VISIBLE
requireActivity().bitmapWithCache(imageUrl, binding!!.photoView, appSettingsService)
Glide
.with(requireActivity())
.asBitmap()
.apply(glideOptions)
.load(imageUrl)
.into(binding!!.photoView)
return view
}

View File

@@ -3,21 +3,26 @@ package bou.amine.apps.readerforselfossv2.android.model
import android.content.Context
import android.webkit.URLUtil
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.android.utils.glide.preloadImage
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.getImages
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
fun SelfossModel.Item.preloadImages(
context: Context,
appSettingsService: AppSettingsService,
): Boolean {
fun SelfossModel.Item.preloadImages(context: Context): Boolean {
val imageUrls = this.getImages()
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000)
try {
for (url in imageUrls) {
if (URLUtil.isValidUrl(url)) {
context.preloadImage(url, appSettingsService)
Glide
.with(context)
.asBitmap()
.apply(glideOptions)
.load(url)
.submit()
}
}
} catch (e: Error) {

View File

@@ -26,10 +26,6 @@ import org.kodein.di.android.closestDI
private const val TITLE_TAG = "settingsActivityTitle"
const val MAX_ITEMS_NUMBER = 200
private const val MIN_ITEMS_NUMBER = 1
class SettingsActivity :
AppCompatActivity(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
@@ -147,7 +143,7 @@ class SettingsActivity :
InputFilter { source, _, _, dest, _, _ ->
try {
val input: Int = (dest.toString() + source.toString()).toInt()
if (input in MIN_ITEMS_NUMBER..MAX_ITEMS_NUMBER) return@InputFilter null
if (input in 1..200) return@InputFilter null
} catch (nfe: NumberFormatException) {
Toast
.makeText(

View File

@@ -2,12 +2,7 @@ package bou.amine.apps.readerforselfossv2.android.utils
import android.content.Context
import android.content.Intent
import android.util.TypedValue
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.fragment.app.Fragment
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
fun Context.shareLink(
@@ -28,32 +23,3 @@ fun Context.shareLink(
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
)
}
@ColorInt
fun Fragment.getColorFromAttr(
@AttrRes attrColor: Int,
resolveRefs: Boolean = true,
): Int {
val typedValue = TypedValue()
maybeIfContextWithLog { this.requireContext().theme.resolveAttribute(attrColor, typedValue, resolveRefs) }
return typedValue.data
}
@Suppress("detekt:SwallowedException")
fun Fragment.maybeIfContext(fn: (Context) -> Any): Any? {
try {
return fn(this.requireContext())
} catch (e: Exception) {
// Do nothing
return null
}
}
fun Fragment.maybeIfContextWithLog(fn: (Context) -> Any): Any? {
try {
return fn(this.requireContext())
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("Fragment context issue...")
return null
}
}

View File

@@ -72,7 +72,6 @@ fun Context.openUrlInBrowser(url: String) {
this.mayBeStartActivity(intent)
}
@Suppress("detekt:SwallowedException")
fun Context.mayBeStartActivity(intent: Intent) {
try {
this.startActivity(intent)

View File

@@ -1,13 +1,6 @@
package bou.amine.apps.readerforselfossv2.android.utils.bottombar
import android.content.Context
import android.graphics.drawable.Drawable
import androidx.annotation.IdRes
import androidx.annotation.StringRes
import bou.amine.apps.readerforselfossv2.android.R
import com.ashokvarma.bottomnavigation.TextBadgeItem
import com.leinardi.android.speeddial.SpeedDialActionItem
import com.leinardi.android.speeddial.SpeedDialView
fun TextBadgeItem.removeBadge(): TextBadgeItem {
this.setText("")
@@ -16,25 +9,3 @@ fun TextBadgeItem.removeBadge(): TextBadgeItem {
}
fun TextBadgeItem.maybeShow(): TextBadgeItem = if (this.isHidden) this.show() else this
@Suppress("detekt:LongParameterList")
fun SpeedDialView.addHomeMadeActionItem(
@IdRes actionId: Int,
actionIcon: Drawable,
@StringRes labelId: Int,
colorOnSurface: Int,
colorSurface: Int,
context: Context,
) {
this.addActionItem(
SpeedDialActionItem
.Builder(actionId, actionIcon)
.setFabBackgroundColor(context.resources.getColor(R.color.colorAccent))
.setFabImageTintColor(colorOnSurface)
.setLabel(context.getString(labelId))
.setLabelClickable(false)
.setLabelBackgroundColor(colorOnSurface)
.setLabelColor(colorSurface)
.create(),
)
}

View File

@@ -2,135 +2,42 @@ package bou.amine.apps.readerforselfossv2.android.utils.glide
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.webkit.WebView
import android.widget.ImageView
import bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.LazyHeaders
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.ViewTarget
import com.google.android.material.chip.Chip
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
private const val PRELOAD_IMAGE_TIMEOUT = 10000
@Suppress("detekt:ReturnCount")
@OptIn(ExperimentalEncodingApi::class)
fun String.toGlideUrl(appSettingsService: AppSettingsService): Any { // GlideUrl Or String
if (this.isEmptyOrNullOrNullString()) {
return ""
}
if (appSettingsService.getBasicUserName().isNotEmpty()) {
val authString = "${appSettingsService.getBasicUserName()}:${appSettingsService.getBasicPassword()}"
val authBuf = Base64.encode(authString.toByteArray(Charsets.UTF_8))
return GlideUrl(
this,
LazyHeaders
.Builder()
.addHeader("Authorization", "Basic $authBuf")
.build(),
)
} else {
return GlideUrl(
this,
)
}
}
fun WebView.getGlideImageForResource(
url: String,
appSettingsService: AppSettingsService,
) = Glide
.with(this)
.asBitmap()
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL))
.load(url.toGlideUrl(appSettingsService))
.submit()
.get()
fun Context.preloadImage(
url: String,
appSettingsService: AppSettingsService,
) = Glide
.with(this)
.asBitmap()
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(PRELOAD_IMAGE_TIMEOUT))
.load(url.toGlideUrl(appSettingsService))
.submit()
fun Context.imageIntoViewTarget(
url: String,
target: ViewTarget<Chip?, Drawable?>,
appSettingsService: AppSettingsService,
) = Glide
.with(this)
.load(url.toGlideUrl(appSettingsService))
.into(target)
fun Context.bitmapWithCache(
url: String,
iv: ImageView,
appSettingsService: AppSettingsService,
) = Glide
.with(this)
.asBitmap()
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL))
.load(url.toGlideUrl(appSettingsService))
.into(iv)
fun Context.bitmapCenterCrop(
url: String,
iv: ImageView,
appSettingsService: AppSettingsService,
) = Glide
.with(this)
.asBitmap()
.load(url.toGlideUrl(appSettingsService))
.load(url)
.apply(RequestOptions.centerCropTransform())
.into(iv)
fun Context.bitmapFitCenter(
url: String,
iv: ImageView,
appSettingsService: AppSettingsService,
) = Glide
.with(this)
.asBitmap()
.load(url.toGlideUrl(appSettingsService))
.apply(RequestOptions.fitCenterTransform())
.into(iv)
fun Context.circularDrawable(
url: String,
view: CircleImageView,
appSettingsService: AppSettingsService,
) {
view.textView.text = ""
Glide
.with(this)
.load(url.toGlideUrl(appSettingsService))
.load(url)
.into(view.imageView)
}
private const val BITMAP_INPUT_STREAM_COMPRESSION_QUALITY = 80
fun getBitmapInputStream(
bitmap: Bitmap,
compressFormat: Bitmap.CompressFormat,
): InputStream {
val byteArrayOutputStream = ByteArrayOutputStream()
bitmap.compress(compressFormat, BITMAP_INPUT_STREAM_COMPRESSION_QUALITY, byteArrayOutputStream)
bitmap.compress(compressFormat, 80, byteArrayOutputStream)
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
return ByteArrayInputStream(bitmapData)
}

View File

@@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.android.utils.network
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import com.google.android.material.snackbar.Snackbar
lateinit var s: Snackbar
@@ -10,13 +11,19 @@ lateinit var s: Snackbar
fun isNetworkAccessible(context: Context): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) ?: return false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = connectivityManager.activeNetwork ?: return false
val networkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
return when {
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
else -> false
return when {
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
else -> false
}
} else {
val network = connectivityManager.activeNetworkInfo ?: return false
return network.isConnectedOrConnecting
}
}

View File

@@ -71,13 +71,35 @@
</androidx.core.widget.NestedScrollView>
<com.leinardi.android.speeddial.SpeedDialView
android:id="@+id/speedDial"
android:layout_width="wrap_content"
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
app:layout_behavior="@string/speeddial_scrolling_view_snackbar_behavior"
app:sdMainFabClosedSrc="@drawable/ic_add_white_24dp" />
android:layout_gravity="start|bottom|end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.github.rubensousa.floatingtoolbar.FloatingToolbar
android:id="@+id/floatingToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
app:floatingMenu="@menu/reader_toolbar" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:src="@drawable/ic_add_white_24dp"
app:backgroundTint="?attr/colorAccent"
app:fabSize="mini"
app:rippleColor="?attr/colorAccentDark" />
</FrameLayout>
<FrameLayout
android:id="@+id/progressBar"
@@ -97,5 +119,4 @@
android:progressTint="?attr/colorAccent" />
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/unread_action"
android:icon="@drawable/ic_baseline_white_eye_24dp"
android:title="@string/unmark"
app:showAsAction="ifRoom" />
<item
android:id="@+id/open_action"
android:icon="@drawable/ic_open_in_browser_white_24dp"
android:title="@string/reader_action_open"
app:showAsAction="ifRoom" />
<item
android:id="@+id/share_action"
android:icon="@drawable/ic_share_white_24dp"
android:title="@string/reader_action_share"
app:showAsAction="ifRoom" />
</menu>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
@@ -129,4 +132,4 @@
<string name="action_about">"Quant a"</string>
<string name="marked_as_read">"Element llegit"</string>
<string name="marked_as_unread">"Item unread"</string>
</resources>
</resources>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">Linksbündig</string>
<string name="reader_text_align_justify">Blocksatz</string>
<string name="settings_reader_font">Schriftgröße im Lesemodus</string>
<string name="reader_static_bar_title">Statische untere Leiste im Lesemodus</string>
<string name="reader_static_bar_on">Die untere Leiste wird dauerhaft angezeigt</string>
<string name="reader_static_bar_off">Die untere Leiste kann über einen schwebenden Button angezeigt werden</string>
<string name="remove_source">Quelle entfernen</string>
<string name="pref_theme_title">Heller/Dunkler Modus</string>
<string name="mode_dark">Dunkler Modus</string>
@@ -129,4 +132,4 @@
<string name="action_about">"Über"</string>
<string name="marked_as_read">"Artikel gelesen"</string>
<string name="marked_as_unread">"Item unread"</string>
</resources>
</resources>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">Alinear a la izquierda</string>
<string name="reader_text_align_justify">Justificado</string>
<string name="settings_reader_font">Modo lectura</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
@@ -129,4 +132,4 @@
<string name="action_about">"Acerca de"</string>
<string name="marked_as_read">"Artículo leído"</string>
<string name="marked_as_unread">"Artículo no leído"</string>
</resources>
</resources>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">Aligner à gauche</string>
<string name="reader_text_align_justify">Justifier le texte</string>
<string name="settings_reader_font">Police du lecteur d\'articles</string>
<string name="reader_static_bar_title">Barre statique pour le visionneur d\'articles</string>
<string name="reader_static_bar_on">La barre sera affichée</string>
<string name="reader_static_bar_off">La barre sera affichée grâce au bouton</string>
<string name="remove_source">Supprimer la source</string>
<string name="pref_theme_title">Thème Clair/Sombre</string>
<string name="mode_dark">Thème sombre</string>
@@ -129,4 +132,4 @@
<string name="action_about">"À propos"</string>
<string name="marked_as_read">"Marqué comme lu"</string>
<string name="marked_as_unread">"Marqué comme non lu"</string>
</resources>
</resources>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">Aliñar á esquerda</string>
<string name="reader_text_align_justify">Xustificado</string>
<string name="settings_reader_font">Modo lector</string>
<string name="reader_static_bar_title">Barra inferior estática na vista de artigos</string>
<string name="reader_static_bar_on">A barra inferior mostrarase sempre</string>
<string name="reader_static_bar_off">A barra inferior pode mostrarse a través do botón flotante</string>
<string name="remove_source">Eliminar fonte</string>
<string name="pref_theme_title">Modo Claro/Escuro</string>
<string name="mode_dark">Modo escuro</string>
@@ -129,4 +132,4 @@
<string name="action_about">"Acerca de"</string>
<string name="marked_as_read">"Elemento lido"</string>
<string name="marked_as_unread">"Elemento non lido"</string>
</resources>
</resources>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
@@ -129,4 +132,4 @@
<string name="action_about">"Tentang"</string>
<string name="marked_as_read">"Membaca item"</string>
<string name="marked_as_unread">"Item unread"</string>
</resources>
</resources>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
@@ -129,4 +132,4 @@
<string name="action_about">"Informazioni"</string>
<string name="marked_as_read">"Articolo letto"</string>
<string name="marked_as_unread">"Item unread"</string>
</resources>
</resources>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
@@ -129,4 +132,4 @@
<string name="action_about">"정보"</string>
<string name="marked_as_read">"항목 읽기"</string>
<string name="marked_as_unread">"Item unread"</string>
</resources>
</resources>

View File

@@ -23,7 +23,6 @@
<string name="wrong_infos">"Controleer de gegevens nogmaals."</string>
<string name="all_posts_not_read">"Fout bij markeren als gelezen"</string>
<string name="all_posts_read">"Alle artikelen gemarkeerd als gelezen"</string>
<string name="undo_string">"Ongedaan maken"</string>
<string name="addStringNoUrl">"Login om bronnen toe te voegen"</string>
<string name="cant_get_sources">"Kan de lijst met bronnen niet ophalen"</string>
<string name="cant_create_source">"Kan bron niet creëeren"</string>
@@ -106,6 +105,9 @@
<string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
@@ -129,4 +131,5 @@
<string name="action_about">"Over"</string>
<string name="marked_as_read">"Artikel gelezen"</string>
<string name="marked_as_unread">"Item unread"</string>
</resources>
<string name="undo_string">"Ongedaan maken"</string>
</resources>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
@@ -129,4 +132,4 @@
<string name="action_about">"Sobre"</string>
<string name="marked_as_read">"Item lido"</string>
<string name="marked_as_unread">"Item unread"</string>
</resources>
</resources>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
@@ -129,4 +132,4 @@
<string name="action_about">"Sobre"</string>
<string name="marked_as_read">"Item lido"</string>
<string name="marked_as_unread">"Item unread"</string>
</resources>
</resources>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
@@ -129,4 +132,4 @@
<string name="action_about">"මේ ගැන"</string>
<string name="marked_as_read">"Item read"</string>
<string name="marked_as_unread">"Item unread"</string>
</resources>
</resources>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
@@ -129,4 +132,4 @@
<string name="action_about">"Hakkında"</string>
<string name="marked_as_read">"Öğeleri oku"</string>
<string name="marked_as_unread">"Item unread"</string>
</resources>
</resources>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">左对齐</string>
<string name="reader_text_align_justify">左右对齐</string>
<string name="settings_reader_font">阅读器字体</string>
<string name="reader_static_bar_title">文章查看器中的静态底部栏</string>
<string name="reader_static_bar_on">底部栏将始终显示</string>
<string name="reader_static_bar_off">底部栏可以通过浮动按钮显示</string>
<string name="remove_source">删除源</string>
<string name="pref_theme_title">浅色/深色模式</string>
<string name="mode_dark">深色模式</string>
@@ -129,4 +132,4 @@
<string name="action_about">"关于我们"</string>
<string name="marked_as_read">"已读"</string>
<string name="marked_as_unread">"未读条目"</string>
</resources>
</resources>

View File

@@ -106,6 +106,9 @@
<string name="reader_text_align_left">Align left</string>
<string name="reader_text_align_justify">Justify</string>
<string name="settings_reader_font">Reader font</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
@@ -129,4 +132,4 @@
<string name="action_about">"关于我们"</string>
<string name="marked_as_read">"已读"</string>
<string name="marked_as_unread">"未讀項目"</string>
</resources>
</resources>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="unread_action" type="id" />
<item name="open_action" type="id" />
<item name="share_action" type="id" />
</resources>

View File

@@ -108,6 +108,9 @@
<string name="source_code_pro_font_id" translatable="false">source_code_pro_medium</string>
<string name="open_sans_font_id" translatable="false">open_sans</string>
<string name="roboto_font_id" translatable="false">roboto</string>
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
<string name="remove_source">Remove source</string>
<string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Dark mode</string>
@@ -131,4 +134,4 @@
<string name="action_about">"About"</string>
<string name="marked_as_read">"Item read"</string>
<string name="marked_as_unread">"Item unread"</string>
</resources>
</resources>

View File

@@ -30,6 +30,14 @@
android:summaryOn="@string/pref_article_viewer_on"
android:title="@string/pref_article_viewer_title"
app:iconSpaceReserved="false"/>
<SwitchPreference
android:defaultValue="false"
android:dependency="prefer_article_viewer"
android:key="reader_static_bar"
android:summaryOff="@string/reader_static_bar_off"
android:summaryOn="@string/reader_static_bar_on"
android:title="@string/reader_static_bar_title"
app:iconSpaceReserved="false"/>
<PreferenceCategory
android:title="@string/pref_general_category_displaying">

View File

@@ -1,5 +1,3 @@
@file:Suppress("detekt:LargeClass")
package bou.amine.apps.readerforselfossv2.tests.repository
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
@@ -44,9 +42,9 @@ private const val FEED_URL = "https://test.com/feed"
private const val TAGS = "Test, New"
private const val NUMBER_ARTICLES = 100
private const val NUMBER_UNREAD = 50
private const val NUMBER_STARRED = 20
private val NUMBER_ARTICLES = 100
private val NUMBER_UNREAD = 50
private val NUMBER_STARRED = 20
class RepositoryTest {
private val db = mockk<ReaderForSelfossDB>(relaxed = true)

View File

@@ -1,786 +0,0 @@
build:
maxIssues: 0
excludeCorrectable: false
weights:
# complexity: 2
# LongParameterList: 1
# style: 1
# comments: 1
config:
validation: true
warningsAsErrors: false
checkExhaustiveness: false
# when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
excludes: ''
processors:
active: true
exclude:
- 'DetektProgressListener'
# - 'KtFileCountProcessor'
# - 'PackageCountProcessor'
# - 'ClassCountProcessor'
# - 'FunctionCountProcessor'
# - 'PropertyCountProcessor'
# - 'ProjectComplexityProcessor'
# - 'ProjectCognitiveComplexityProcessor'
# - 'ProjectLLOCProcessor'
# - 'ProjectCLOCProcessor'
# - 'ProjectLOCProcessor'
# - 'ProjectSLOCProcessor'
# - 'LicenseHeaderLoaderExtension'
console-reports:
active: true
exclude:
- 'ProjectStatisticsReport'
- 'ComplexityReport'
- 'NotificationReport'
- 'FindingsReport'
- 'FileBasedFindingsReport'
# - 'LiteFindingsReport'
output-reports:
active: true
exclude:
# - 'TxtOutputReport'
# - 'XmlOutputReport'
# - 'HtmlOutputReport'
# - 'MdOutputReport'
# - 'SarifOutputReport'
comments:
active: true
AbsentOrWrongFileLicense:
active: false
licenseTemplateFile: 'license.template'
licenseTemplateIsRegex: false
CommentOverPrivateFunction:
active: false
CommentOverPrivateProperty:
active: false
DeprecatedBlockTag:
active: false
EndOfSentenceFormat:
active: false
endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
KDocReferencesNonPublicProperty:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
OutdatedDocumentation:
active: false
matchTypeParameters: true
matchDeclarationsOrder: true
allowParamOnConstructorProperties: false
UndocumentedPublicClass:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
searchInNestedClass: true
searchInInnerClass: true
searchInInnerObject: true
searchInInnerInterface: true
searchInProtectedClass: false
UndocumentedPublicFunction:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
searchProtectedFunction: false
UndocumentedPublicProperty:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
searchProtectedProperty: false
complexity:
active: true
CognitiveComplexMethod:
active: false
threshold: 15
ComplexCondition:
active: true
threshold: 4
ComplexInterface:
active: false
threshold: 10
includeStaticDeclarations: false
includePrivateDeclarations: false
ignoreOverloaded: false
CyclomaticComplexMethod:
active: true
threshold: 15
ignoreSingleWhenExpression: false
ignoreSimpleWhenEntries: false
ignoreNestingFunctions: false
nestingFunctions:
- 'also'
- 'apply'
- 'forEach'
- 'isNotNull'
- 'ifNull'
- 'let'
- 'run'
- 'use'
- 'with'
LabeledExpression:
active: false
ignoredLabels: [ ]
LargeClass:
active: true
threshold: 600
LongMethod:
active: true
threshold: 60
LongParameterList:
active: true
functionThreshold: 6
constructorThreshold: 7
ignoreDefaultParameters: false
ignoreDataClasses: true
ignoreAnnotatedParameter: [ ]
MethodOverloading:
active: false
threshold: 6
NamedArguments:
active: false
threshold: 3
ignoreArgumentsMatchingNames: false
NestedBlockDepth:
active: true
threshold: 4
NestedScopeFunctions:
active: false
threshold: 1
functions:
- 'kotlin.apply'
- 'kotlin.run'
- 'kotlin.with'
- 'kotlin.let'
- 'kotlin.also'
ReplaceSafeCallChainWithRun:
active: false
StringLiteralDuplication:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
threshold: 3
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/android/*Activity.kt', '**/fragments/*Fragment.kt' ]
thresholdInFiles: 11
thresholdInClasses: 11
thresholdInInterfaces: 11
thresholdInObjects: 11
thresholdInEnums: 11
ignoreDeprecated: false
ignorePrivate: false
ignoreOverridden: false
ignoreAnnotatedFunctions: [ ]
coroutines:
active: true
GlobalCoroutineUsage:
active: false
InjectDispatcher:
active: true
dispatcherNames:
- 'IO'
- 'Default'
- 'Unconfined'
RedundantSuspendModifier:
active: true
SleepInsteadOfDelay:
active: true
SuspendFunSwallowedCancellation:
active: false
SuspendFunWithCoroutineScopeReceiver:
active: false
SuspendFunWithFlowReturnType:
active: true
empty-blocks:
active: true
EmptyCatchBlock:
active: true
allowedExceptionNameRegex: '_|(ignore|expected).*'
EmptyClassBlock:
active: true
EmptyDefaultConstructor:
active: true
EmptyDoWhileBlock:
active: true
EmptyElseBlock:
active: true
EmptyFinallyBlock:
active: true
EmptyForBlock:
active: true
EmptyFunctionBlock:
active: true
ignoreOverridden: false
EmptyIfBlock:
active: true
EmptyInitBlock:
active: true
EmptyKtFile:
active: true
EmptySecondaryConstructor:
active: true
EmptyTryBlock:
active: true
EmptyWhenBlock:
active: true
EmptyWhileBlock:
active: true
exceptions:
active: true
ExceptionRaisedInUnexpectedLocation:
active: true
methodNames:
- 'equals'
- 'finalize'
- 'hashCode'
- 'toString'
InstanceOfCheckForException:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
NotImplementedDeclaration:
active: false
ObjectExtendsThrowable:
active: false
PrintStackTrace:
active: true
RethrowCaughtException:
active: true
ReturnFromFinally:
active: true
ignoreLabeled: false
SwallowedException:
active: true
ignoredExceptionTypes:
- 'InterruptedException'
- 'MalformedURLException'
- 'NumberFormatException'
- 'ParseException'
allowedExceptionNameRegex: '_|(ignore|expected).*'
ThrowingExceptionFromFinally:
active: true
ThrowingExceptionInMain:
active: false
ThrowingExceptionsWithoutMessageOrCause:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
exceptions:
- 'ArrayIndexOutOfBoundsException'
- 'Exception'
- 'IllegalArgumentException'
- 'IllegalMonitorStateException'
- 'IllegalStateException'
- 'IndexOutOfBoundsException'
- 'NullPointerException'
- 'RuntimeException'
- 'Throwable'
ThrowingNewInstanceOfSameException:
active: true
TooGenericExceptionCaught:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
exceptionNames:
- 'ArrayIndexOutOfBoundsException'
- 'Error'
- 'Exception'
- 'IllegalMonitorStateException'
- 'IndexOutOfBoundsException'
- 'NullPointerException'
- 'RuntimeException'
- 'Throwable'
allowedExceptionNameRegex: '_|(ignore|expected).*'
TooGenericExceptionThrown:
active: true
exceptionNames:
- 'Error'
- 'Exception'
- 'RuntimeException'
- 'Throwable'
naming:
active: true
BooleanPropertyNaming:
active: false
allowedPattern: '^(is|has|are)'
ClassNaming:
active: true
classPattern: '[A-Z][a-zA-Z0-9]*'
ConstructorParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
EnumNaming:
active: true
enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: false
forbiddenName: [ ]
FunctionMaxLength:
active: false
maximumFunctionNameLength: 30
FunctionMinLength:
active: false
minimumFunctionNameLength: 3
FunctionNaming:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
functionPattern: '[a-z][a-zA-Z0-9]*'
excludeClassPattern: '$^'
FunctionParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
InvalidPackageDeclaration:
active: true
rootPackage: ''
requireRootInDeclaration: false
LambdaParameterNaming:
active: false
parameterPattern: '[a-z][A-Za-z0-9]*|_'
MatchingDeclarationName:
active: false # done in ktlint
mustBeFirst: true
MemberNameEqualsClassName:
active: true
ignoreOverridden: true
NoNameShadowing:
active: true
NonBooleanPropertyPrefixedWithIs:
active: false
ObjectPropertyNaming:
active: true
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
TopLevelPropertyNaming:
active: true
constantPattern: '[A-Z][_A-Z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
VariableMaxLength:
active: false
maximumVariableNameLength: 64
VariableMinLength:
active: false
minimumVariableNameLength: 1
VariableNaming:
active: true
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
performance:
active: true
ArrayPrimitive:
active: true
CouldBeSequence:
active: false
threshold: 3
ForEachOnRange:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
SpreadOperator:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
UnnecessaryPartOfBinaryExpression:
active: false
UnnecessaryTemporaryInstantiation:
active: true
potential-bugs:
active: true
AvoidReferentialEquality:
active: true
forbiddenTypePatterns:
- 'kotlin.String'
CastNullableToNonNullableType:
active: false
CastToNullableType:
active: false
Deprecation:
active: false
DontDowncastCollectionTypes:
active: false
DoubleMutabilityForCollection:
active: true
mutableTypes:
- 'kotlin.collections.MutableList'
- 'kotlin.collections.MutableMap'
- 'kotlin.collections.MutableSet'
- 'java.util.ArrayList'
- 'java.util.LinkedHashSet'
- 'java.util.HashSet'
- 'java.util.LinkedHashMap'
- 'java.util.HashMap'
ElseCaseInsteadOfExhaustiveWhen:
active: false
ignoredSubjectTypes: [ ]
EqualsAlwaysReturnsTrueOrFalse:
active: true
EqualsWithHashCodeExist:
active: true
ExitOutsideMain:
active: false
ExplicitGarbageCollectionCall:
active: true
HasPlatformType:
active: true
IgnoredReturnValue:
active: true
restrictToConfig: true
returnValueAnnotations:
- 'CheckResult'
- '*.CheckResult'
- 'CheckReturnValue'
- '*.CheckReturnValue'
ignoreReturnValueAnnotations:
- 'CanIgnoreReturnValue'
- '*.CanIgnoreReturnValue'
returnValueTypes:
- 'kotlin.sequences.Sequence'
- 'kotlinx.coroutines.flow.*Flow'
- 'java.util.stream.*Stream'
ignoreFunctionCall: [ ]
ImplicitDefaultLocale:
active: true
ImplicitUnitReturnType:
active: false
allowExplicitReturnType: true
InvalidRange:
active: true
IteratorHasNextCallsNextMethod:
active: true
IteratorNotThrowingNoSuchElementException:
active: true
LateinitUsage:
active: false
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
ignoreOnClassesPattern: ''
MapGetWithNotNullAssertionOperator:
active: true
MissingPackageDeclaration:
active: false
excludes: [ '**/*.kts' ]
NullCheckOnMutableProperty:
active: false
NullableToStringCall:
active: false
PropertyUsedBeforeDeclaration:
active: false
UnconditionalJumpStatementInLoop:
active: false
UnnecessaryNotNullCheck:
active: false
UnnecessaryNotNullOperator:
active: true
UnnecessarySafeCall:
active: true
UnreachableCatchBlock:
active: true
UnreachableCode:
active: true
UnsafeCallOnNullableType:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
UnsafeCast:
active: true
UnusedUnaryOperator:
active: true
UselessPostfixExpression:
active: true
WrongEqualsTypeParameter:
active: true
style:
active: true
AlsoCouldBeApply:
active: false
BracesOnIfStatements:
active: false
singleLine: 'never'
multiLine: 'always'
BracesOnWhenStatements:
active: false
singleLine: 'necessary'
multiLine: 'consistent'
CanBeNonNullable:
active: false
CascadingCallWrapping:
active: false
includeElvis: true
ClassOrdering:
active: false
CollapsibleIfStatements:
active: false
DataClassContainsFunctions:
active: false
conversionFunctionPrefix:
- 'to'
allowOperators: false
DataClassShouldBeImmutable:
active: false
DestructuringDeclarationWithTooManyEntries:
active: true
maxDestructuringEntries: 3
DoubleNegativeLambda:
active: false
negativeFunctions:
- reason: 'Use `takeIf` instead.'
value: 'takeUnless'
- reason: 'Use `all` instead.'
value: 'none'
negativeFunctionNameParts:
- 'not'
- 'non'
EqualsNullCall:
active: true
EqualsOnSignatureLine:
active: false
ExplicitCollectionElementAccessMethod:
active: false
ExplicitItLambdaParameter:
active: true
ExpressionBodySyntax:
active: false
includeLineWrapping: false
ForbiddenAnnotation:
active: false
annotations:
- reason: 'it is a java annotation. Use `Suppress` instead.'
value: 'java.lang.SuppressWarnings'
- reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.'
value: 'java.lang.Deprecated'
- reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.'
value: 'java.lang.annotation.Documented'
- reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.'
value: 'java.lang.annotation.Target'
- reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.'
value: 'java.lang.annotation.Retention'
- reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.'
value: 'java.lang.annotation.Repeatable'
- reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265'
value: 'java.lang.annotation.Inherited'
ForbiddenComment:
active: true
comments:
- reason: 'Forbidden FIXME todo marker in comment, please fix the problem.'
value: 'FIXME:'
- reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.'
value: 'STOPSHIP:'
- reason: 'Forbidden TODO todo marker in comment, please do the changes.'
value: 'TODO:'
allowedPatterns: ''
ForbiddenImport:
active: false
imports: [ ]
forbiddenPatterns: ''
ForbiddenMethodCall:
active: false
methods:
- reason: 'print does not allow you to configure the output stream. Use a logger instead.'
value: 'kotlin.io.print'
- reason: 'println does not allow you to configure the output stream. Use a logger instead.'
value: 'kotlin.io.println'
ForbiddenSuppress:
active: false
rules: [ ]
ForbiddenVoid:
active: true
ignoreOverridden: false
ignoreUsageInGenerics: false
FunctionOnlyReturningConstant:
active: true
ignoreOverridableFunction: true
ignoreActualFunction: true
excludedFunctions: [ ]
LoopWithTooManyJumpStatements:
active: true
maxJumpCount: 1
MagicNumber:
active: true
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts' ]
ignoreNumbers:
- '-1'
- '0'
- '1'
- '2'
ignoreHashCodeFunction: true
ignorePropertyDeclaration: false
ignoreLocalVariableDeclaration: false
ignoreConstantDeclaration: true
ignoreCompanionObjectPropertyDeclaration: true
ignoreAnnotation: false
ignoreNamedArgument: true
ignoreEnums: false
ignoreRanges: false
ignoreExtensionFunctions: true
MandatoryBracesLoops:
active: false
MaxChainedCallsOnSameLine:
active: false
maxChainedCalls: 5
MaxLineLength:
active: false # done in ktlint
maxLineLength: 140 # default is 120. 140 to match ktlint
excludePackageStatements: true
excludeImportStatements: true
excludeCommentStatements: false
excludeRawStrings: true
MayBeConst:
active: true
ModifierOrder:
active: true
MultilineLambdaItParameter:
active: false
MultilineRawStringIndentation:
active: false
indentSize: 4
trimmingMethods:
- 'trimIndent'
- 'trimMargin'
NestedClassesVisibility:
active: true
NewLineAtEndOfFile:
active: false # done in ktlint
NoTabs:
active: false
NullableBooleanCheck:
active: false
ObjectLiteralToLambda:
active: true
OptionalAbstractKeyword:
active: true
OptionalUnit:
active: false
PreferToOverPairSyntax:
active: false
ProtectedMemberInFinalClass:
active: true
RedundantExplicitType:
active: false
RedundantHigherOrderMapUsage:
active: true
RedundantVisibilityModifierRule:
active: false
ReturnCount:
active: true
max: 2
excludedFunctions:
- 'equals'
excludeLabeled: false
excludeReturnFromLambda: true
excludeGuardClauses: false
SafeCast:
active: true
SerialVersionUIDInSerializableClass:
active: true
SpacingBetweenPackageAndImports:
active: false
StringShouldBeRawString:
active: false
maxEscapedCharacterCount: 2
ignoredCharacters: [ ]
ThrowsCount:
active: true
max: 2
excludeGuardClauses: false
TrailingWhitespace:
active: false
TrimMultilineRawString:
active: false
trimmingMethods:
- 'trimIndent'
- 'trimMargin'
UnderscoresInNumericLiterals:
active: false
acceptableLength: 4
allowNonStandardGrouping: false
UnnecessaryAbstractClass:
active: true
UnnecessaryAnnotationUseSiteTarget:
active: false
UnnecessaryApply:
active: true
UnnecessaryBackticks:
active: false
UnnecessaryBracesAroundTrailingLambda:
active: false
UnnecessaryFilter:
active: true
UnnecessaryInheritance:
active: true
UnnecessaryInnerClass:
active: false
UnnecessaryLet:
active: false
UnnecessaryParentheses:
active: false
allowForUnclearPrecedence: false
UntilInsteadOfRangeTo:
active: false
UnusedImports:
active: false
UnusedParameter:
active: true
allowedNames: 'ignored|expected'
UnusedPrivateClass:
active: true
UnusedPrivateMember:
active: true
allowedNames: ''
UnusedPrivateProperty:
active: true
allowedNames: '_|ignored|expected|serialVersionUID'
excludes: [ '**/build.gradle.kts' ]
UseAnyOrNoneInsteadOfFind:
active: true
UseArrayLiteralsInAnnotations:
active: true
UseCheckNotNull:
active: true
UseCheckOrError:
active: true
UseDataClass:
active: false
allowVars: false
UseEmptyCounterpart:
active: false
UseIfEmptyOrIfBlank:
active: false
UseIfInsteadOfWhen:
active: false
ignoreWhenContainingVariableDeclaration: false
UseIsNullOrEmpty:
active: true
UseLet:
active: false
UseOrEmpty:
active: true
UseRequire:
active: true
UseRequireNotNull:
active: true
UseSumOfInsteadOfFlatMapSize:
active: false
UselessCallOnNotNull:
active: true
UtilityClassWithPublicConstructor:
active: true
VarCouldBeVal:
active: true
ignoreLateinitVar: false
WildcardImport:
active: true
excludeImports:
- 'java.util.*'

View File

@@ -1,4 +0,0 @@
**v125010111**
- Debug trying to fix context issues. (#174)
- Changelog for v125010031

View File

@@ -1,5 +0,0 @@
**v125010131**
- fix: reload the adapter when it's needed. Fixes #128. (#176)
- feat: basic auth and images loading. Fixes #172. (#175)
- Changelog for v125010111

View File

@@ -1,6 +0,0 @@
**v125010201**
- fix: Handle empty url issue.
- Merge pull request 'Removed the floating bar.' (#177) from floating-bar into master
- chore: changing actions in reader fragment.
- Changelog for v125010131

View File

@@ -1,8 +0,0 @@
**v125010241**
- Merge pull request 'fix: Link not opening.' (#178) from fix-open-link into master
- refactor: context fragments issues.
- logs: Context issues.
- fix: Handle empty url issue, again.
- fix: Link not opening.
- Changelog for v125010201

View File

@@ -18,7 +18,7 @@ kotlin.code.style=official
#Android
android.useAndroidX=true
#android.nonTransitiveRClass=true
android.enableJetifier=false
android.enableJetifier=true
android.nonTransitiveRClass=false
#MPP
kotlin.mpp.enableCInteropCommonization=true

View File

@@ -9,14 +9,12 @@ class NaiveTrustManager : X509TrustManager {
chain: Array<out X509Certificate>?,
authType: String?,
) {
// Nothing
}
override fun checkServerTrusted(
chain: Array<out X509Certificate>?,
authType: String?,
) {
// Nothing
}
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()

View File

@@ -3,7 +3,6 @@ package bou.amine.apps.readerforselfossv2.utils
import android.text.format.DateUtils
import kotlinx.datetime.Clock
@Suppress("detekt:UtilityClassWithPublicConstructor")
actual class DateUtils {
actual companion object {
actual fun parseRelativeDate(dateString: String): String {

View File

@@ -12,14 +12,16 @@ actual fun SelfossModel.Item.getIcon(baseUrl: String): String = constructUrl(bas
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String = constructUrl(baseUrl, "thumbnails", thumbnail)
val IMAGE_EXTENSION_REGEXP = """\.(jpg|jpeg|png|webp)""".toRegex()
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
val allImages = ArrayList<String>()
for (image in Jsoup.parse(content).getElementsByTag("img")) {
val url = image.attr("src")
if (IMAGE_EXTENSION_REGEXP.containsMatchIn(url.lowercase(Locale.US))) {
if (url.lowercase(Locale.US).contains(".jpg") ||
url.lowercase(Locale.US).contains(".jpeg") ||
url.lowercase(Locale.US).contains(".png") ||
url.lowercase(Locale.US).contains(".webp")
) {
allImages.add(url)
}
}

View File

@@ -1,16 +1,13 @@
@file:Suppress("detekt:LongParameterList")
package bou.amine.apps.readerforselfossv2.model
import kotlinx.serialization.Serializable
class MercuryModel {
@Suppress("detekt:ConstructorParameterNaming")
@Serializable
class ParsedContent(
val title: String? = null,
val content: String? = null,
val lead_image_url: String? = null,
val lead_image_url: String? = null, // NOSONAR
val url: String? = null,
val error: Boolean? = null,
val message: String? = null,

View File

@@ -1,5 +1,3 @@
@file:Suppress("detekt:LongParameterList")
package bou.amine.apps.readerforselfossv2.model
import bou.amine.apps.readerforselfossv2.utils.DateUtils
@@ -20,10 +18,6 @@ import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonPrimitive
class ModelException(
message: String,
) : Throwable(message)
class SelfossModel {
@Serializable
data class Tag(
@@ -147,7 +141,7 @@ class SelfossModel {
}
if (stringUrl.isEmptyOrNullOrNullString()) {
throw ModelException("Link $link was translated to $stringUrl, but was empty. Handle this.")
throw Exception("Link $link was translated to $stringUrl, but was empty. Handle this.")
}
return stringUrl
@@ -176,7 +170,7 @@ class SelfossModel {
}
}
// this seems to be super slow.
// TODO: this seems to be super slow.
object TagsListSerializer : KSerializer<List<String>> {
override fun deserialize(decoder: Decoder): List<String> =
when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {

View File

@@ -1,5 +1,3 @@
@file:Suppress("detekt:TooManyFunctions")
package bou.amine.apps.readerforselfossv2.repository
import bou.amine.apps.readerforselfossv2.dao.ACTION
@@ -25,8 +23,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
private const val MAX_ITEMS_NUMBER = 200
class Repository(
private val api: SelfossApi,
private val appSettingsService: AppSettingsService,
@@ -131,7 +127,7 @@ class Repository(
null,
null,
null,
MAX_ITEMS_NUMBER,
200,
)
return if (items.success && items.data != null) {
items.data
@@ -143,7 +139,6 @@ class Repository(
}
}
@Suppress("detekt:ForbiddenComment")
suspend fun reloadBadges(): Boolean {
var success = false
if (isNetworkAvailable()) {
@@ -564,7 +559,6 @@ class Repository(
item.id.toString(),
)
@Suppress("detekt:SwallowedException")
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
try {
val newItems = getMaxItemsForBackground(ItemType.UNREAD)

View File

@@ -33,7 +33,6 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse =
SuccessResponse(false)
}
@Suppress("detekt:SwallowedException")
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
try {
return if (r != null && r.status.isSuccess()) {

View File

@@ -1,5 +1,3 @@
@file:Suppress("detekt:TooManyFunctions", "detekt:LongParameterList", "detekt:LargeClass")
package bou.amine.apps.readerforselfossv2.rest
import bou.amine.apps.readerforselfossv2.model.SelfossModel
@@ -37,8 +35,6 @@ import kotlinx.serialization.json.Json
expect fun setupInsecureHttpEngine(config: CIOEngineConfig)
private const val VERSION_WHERE_POST_LOGIN_SHOULD_WORK = 5
class SelfossApi(
private val appSettingsService: AppSettingsService,
) {
@@ -180,7 +176,7 @@ class SelfossApi(
},
)
private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= VERSION_WHERE_POST_LOGIN_SHOULD_WORK // We are missing 4.1.0
private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
suspend fun logout(): SuccessResponse =
if (shouldHaveNewLogout()) {

View File

@@ -1,5 +1,3 @@
@file:Suppress("detekt:TooManyFunctions")
package bou.amine.apps.readerforselfossv2.service
import com.russhwolf.settings.Settings

View File

@@ -1,19 +1,7 @@
@file:Suppress("detekt:TooManyFunctions")
package bou.amine.apps.readerforselfossv2.service
import com.russhwolf.settings.Settings
private const val DEFAULT_FONT_SIZE = 16
private const val DEFAULT_REFRESH_MINUTES = 360L
private const val MIN_REFRESH_MINUTES = 15L
private const val DEFAULT_API_TIMEOUT = 60L
private const val DEFAULT_ITEMS_NUMBER = 20
class AppSettingsService(
acraSenderServiceProcess: Boolean = false,
) {
@@ -48,11 +36,12 @@ class AppSettingsService(
private var notifyNewItems: Boolean? = null
private var itemsNumber: Int? = null
private var apiTimeout: Long? = null
private var refreshMinutes: Long = DEFAULT_REFRESH_MINUTES
private var refreshMinutes: Long = 360
private var markOnScroll: Boolean? = null
private var activeAlignment: Int? = null
private var fontSize: Int? = null
private var staticBar: Boolean? = null
private var font: String = ""
private var theme: Int? = null
@@ -152,14 +141,13 @@ class AppSettingsService(
return itemsNumber!!
}
@Suppress("detekt:SwallowedException")
private fun refreshItemsNumber() {
itemsNumber =
try {
settings.getString(API_ITEMS_NUMBER, DEFAULT_ITEMS_NUMBER.toString()).toInt()
settings.getString(API_ITEMS_NUMBER, "20").toInt()
} catch (e: Exception) {
settings.remove(API_ITEMS_NUMBER)
DEFAULT_ITEMS_NUMBER
20
}
}
@@ -170,24 +158,22 @@ class AppSettingsService(
return apiTimeout!!
}
@Suppress("detekt:MagicNumber")
private fun secToMs(n: Long) = n * 1000
@Suppress("detekt:SwallowedException")
private fun refreshApiTimeout() {
apiTimeout =
secToMs(
try {
val settingsTimeout = settings.getString(API_TIMEOUT, DEFAULT_API_TIMEOUT.toString())
val settingsTimeout = settings.getString(API_TIMEOUT, "60")
if (settingsTimeout.toLong() > 0) {
settingsTimeout.toLong()
} else {
settings.remove(API_TIMEOUT)
DEFAULT_API_TIMEOUT
60
}
} catch (e: Exception) {
settings.remove(API_TIMEOUT)
DEFAULT_API_TIMEOUT
60
},
)
}
@@ -301,14 +287,14 @@ class AppSettingsService(
}
private fun refreshRefreshMinutes() {
refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, DEFAULT_REFRESH_MINUTES.toString()).toLong()
if (refreshMinutes <= MIN_REFRESH_MINUTES) {
refreshMinutes = MIN_REFRESH_MINUTES
refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, "360").toLong()
if (refreshMinutes <= 15) {
refreshMinutes = 15
}
}
fun getRefreshMinutes(): Long {
if (refreshMinutes != DEFAULT_REFRESH_MINUTES) {
if (refreshMinutes != 360L) {
refreshRefreshMinutes()
}
return refreshMinutes
@@ -382,7 +368,18 @@ class AppSettingsService(
if (fontSize != null) {
refreshFontSize()
}
return fontSize ?: DEFAULT_FONT_SIZE
return fontSize ?: 16
}
private fun refreshStaticBarEnabled() {
staticBar = settings.getBoolean(READER_STATIC_BAR, false)
}
fun isStaticBarEnabled(): Boolean {
if (staticBar != null) {
refreshStaticBarEnabled()
}
return staticBar == true
}
private fun refreshFont() {
@@ -437,6 +434,7 @@ class AppSettingsService(
refreshActiveAllignment()
refreshFontSize()
refreshFont()
refreshStaticBarEnabled()
refreshCurrentTheme()
}
@@ -534,6 +532,8 @@ class AppSettingsService(
const val READER_FONT = "reader_font"
const val READER_STATIC_BAR = "reader_static_bar"
const val READER_FONT_SIZE = "reader_font_size"
const val TEXT_ALIGN = "text_align"

View File

@@ -4,12 +4,6 @@ import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
class DateParseException(
message: String,
e: Throwable? = null,
) : Throwable(message, e)
@Suppress("detekt:ThrowsCount")
fun String.toParsedDate(): Long {
// Possible formats are
// yyyy-mm-dd hh:mm:ss format
@@ -27,18 +21,17 @@ fun String.toParsedDate(): Long {
.find(this)
?.groups
?.get(1)
?.value ?: throw DateParseException("Couldn't parse $this")
?.value ?: throw Exception("Couldn't parse $this")
} else {
throw DateParseException("Unrecognized format for $this")
throw Exception("Unrecognized format for $this")
}
} catch (e: Exception) {
throw DateParseException("parseDate failed for $this", e)
throw Exception("parseDate failed for $this", e)
}
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
}
@Suppress("detekt:UtilityClassWithPublicConstructor")
expect class DateUtils() {
companion object {
fun parseRelativeDate(dateString: String): String

View File

@@ -17,7 +17,7 @@ fun SOURCE.toView(): SelfossModel.SourceDetail =
this.id.toInt(),
this.title,
null,
this.tags.split(","),
this.tags?.split(","),
this.spout,
this.error,
this.icon,
@@ -74,7 +74,6 @@ fun SelfossModel.Item.toEntity(): ITEM =
this.author,
)
@Suppress("detekt:MagicNumber")
fun SelfossModel.Tag.getColorHexCode(): String =
if (this.color.length == 4) { // #000
val char1 = this.color.get(1)

View File

@@ -1,6 +1,5 @@
package bou.amine.apps.readerforselfossv2.utils
@Suppress("detekt:MagicNumber")
enum class ItemType(
val position: Int,
val type: String,

View File

@@ -2,7 +2,6 @@ package bou.amine.apps.readerforselfossv2.utils
fun String?.isEmptyOrNullOrNullString(): Boolean = this == null || this == "null" || this.isEmpty()
@Suppress("detekt:MagicNumber")
fun String.longHash(): Long {
var h = 98764321261L
val l = this.length

View File

@@ -2,6 +2,5 @@ package bou.amine.apps.readerforselfossv2.rest
import io.ktor.client.engine.cio.CIOEngineConfig
@Suppress("detekt:EmptyFunctionBlock")
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
}

View File

@@ -1,6 +1,5 @@
package bou.amine.apps.readerforselfossv2.utils
@Suppress("detekt:UtilityClassWithPublicConstructor")
actual class DateUtils {
actual companion object {
actual fun parseRelativeDate(dateString: String): String {

View File

@@ -1,6 +1,5 @@
package bou.amine.apps.readerforselfossv2.utils
@Suppress("detekt:UtilityClassWithPublicConstructor")
actual class DateUtils actual constructor() {
actual companion object {
actual fun parseRelativeDate(dateString: String): String {

View File

@@ -2,6 +2,5 @@ package bou.amine.apps.readerforselfossv2.rest
import io.ktor.client.engine.cio.CIOEngineConfig
@Suppress("detekt:EmptyFunctionBlock")
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
}

View File

@@ -1,6 +1,5 @@
package bou.amine.apps.readerforselfossv2.utils
@Suppress("detekt:UtilityClassWithPublicConstructor")
actual class DateUtils {
actual companion object {
actual fun parseRelativeDate(dateString: String): String {