Compare commits
2 Commits
v125010291
...
4fbebf2954
Author | SHA1 | Date | |
---|---|---|---|
4fbebf2954 | |||
5035392aff |
@@ -16,13 +16,13 @@ jobs:
|
|||||||
java-version: '17'
|
java-version: '17'
|
||||||
cache: gradle
|
cache: gradle
|
||||||
- name: Install klint
|
- 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
|
- 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...
|
- name: Linting...
|
||||||
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build'
|
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build'
|
||||||
- name: Detecting...
|
- 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:
|
build:
|
||||||
needs: Lint
|
needs: Lint
|
||||||
uses: ./.gitea/workflows/common_build.yml
|
uses: ./.gitea/workflows/common_build.yml
|
||||||
|
35
CHANGELOG.md
35
CHANGELOG.md
@@ -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
|
**v125010031
|
||||||
|
|
||||||
- Merge pull request 'Bump dependencies' (#173) from upgarde into master
|
- Merge pull request 'Bump dependencies' (#173) from upgarde into master
|
||||||
|
@@ -156,7 +156,7 @@ dependencies {
|
|||||||
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
|
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
|
||||||
|
|
||||||
// Themes
|
// Themes
|
||||||
implementation("com.leinardi.android:speed-dial:3.3.0")
|
implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
|
||||||
|
|
||||||
// Pager
|
// Pager
|
||||||
implementation("me.relex:circleindicator:2.1.6")
|
implementation("me.relex:circleindicator:2.1.6")
|
||||||
|
@@ -30,16 +30,21 @@ fun withError(
|
|||||||
): TypeSafeMatcher<View?> {
|
): TypeSafeMatcher<View?> {
|
||||||
return object : TypeSafeMatcher<View?>() {
|
return object : TypeSafeMatcher<View?>() {
|
||||||
override fun matchesSafely(view: View?): Boolean {
|
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
|
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?) {
|
override fun describeTo(description: Description?) {
|
||||||
// Nothing
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,7 +58,6 @@ fun withDrawable(
|
|||||||
description.appendText("ImageView with drawable same as drawable with id $id")
|
description.appendText("ImageView with drawable same as drawable with id $id")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
override fun matchesSafely(view: View): Boolean {
|
override fun matchesSafely(view: View): Boolean {
|
||||||
val context = view.context
|
val context = view.context
|
||||||
val expectedBitmap = context.getDrawable(id)!!.toBitmap()
|
val expectedBitmap = context.getDrawable(id)!!.toBitmap()
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android
|
package bou.amine.apps.readerforselfossv2.android
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import androidx.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.IdlingRegistry
|
import androidx.test.espresso.IdlingRegistry
|
||||||
import androidx.test.espresso.action.ViewActions.click
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
@@ -25,6 +26,14 @@ class LoginActivityTest {
|
|||||||
@get:Rule
|
@get:Rule
|
||||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
|
private fun getActivity(): Activity? {
|
||||||
|
var activity: Activity? = null
|
||||||
|
activityRule.scenario.onActivity {
|
||||||
|
activity = it
|
||||||
|
}
|
||||||
|
return activity
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun registerIdlingResource() {
|
fun registerIdlingResource() {
|
||||||
IdlingRegistry
|
IdlingRegistry
|
||||||
|
@@ -42,7 +42,6 @@ class SettingsActivityGeneralTest {
|
|||||||
onView(withText(R.string.pref_header_general)).perform(click())
|
onView(withText(R.string.pref_header_general)).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:LongMethod")
|
|
||||||
@Test
|
@Test
|
||||||
fun testGeneral() {
|
fun testGeneral() {
|
||||||
onView(withText(R.string.pref_api_items_number_title)).check(matches(isDisplayed()))
|
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(withText(R.string.pref_general_category_displaying)).check(matches(isDisplayed()))
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).check(
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).check(
|
||||||
matches(
|
matches(
|
||||||
@@ -106,7 +118,6 @@ class SettingsActivityGeneralTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:ForbiddenComment")
|
|
||||||
@Test
|
@Test
|
||||||
fun testGeneralActionsNumberItems() {
|
fun testGeneralActionsNumberItems() {
|
||||||
onView(withText(R.string.pref_api_items_number_title)).perform(click())
|
onView(withText(R.string.pref_api_items_number_title)).perform(click())
|
||||||
@@ -148,6 +159,19 @@ class SettingsActivityGeneralTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGeneralActionsCheckboxes() {
|
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(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(not(isEnabled())))
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).perform(click())
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).perform(click())
|
||||||
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(isEnabled()))
|
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(isEnabled()))
|
||||||
|
@@ -42,7 +42,6 @@ class SettingsActivityOfflineTest {
|
|||||||
onView(withText(R.string.pref_header_offline)).perform(click())
|
onView(withText(R.string.pref_header_offline)).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:LongMethod")
|
|
||||||
@Test
|
@Test
|
||||||
fun testOffline() {
|
fun testOffline() {
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).check(
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).check(
|
||||||
@@ -108,7 +107,6 @@ class SettingsActivityOfflineTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:LongMethod")
|
|
||||||
@Test
|
@Test
|
||||||
fun testOfflineActions() {
|
fun testOfflineActions() {
|
||||||
onView(withText(R.string.pref_switch_items_caching_off)).check(matches(isDisplayed()))
|
onView(withText(R.string.pref_switch_items_caching_off)).check(matches(isDisplayed()))
|
||||||
|
@@ -45,7 +45,6 @@ class SourcesActivityTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
@Test
|
@Test
|
||||||
fun addSourceCheckContent() {
|
fun addSourceCheckContent() {
|
||||||
testAddSourceWithUrl("https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en", sourceName)
|
testAddSourceWithUrl("https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en", sourceName)
|
||||||
|
@@ -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.testing.CountingIdlingResourceSingleton
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
|
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge
|
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowserAsNewTask
|
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
@@ -49,8 +49,6 @@ import org.kodein.di.instance
|
|||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
private const val MIN_WIDTH_CARD_DP = 300
|
|
||||||
|
|
||||||
class HomeActivity :
|
class HomeActivity :
|
||||||
AppCompatActivity(),
|
AppCompatActivity(),
|
||||||
SearchView.OnQueryTextListener,
|
SearchView.OnQueryTextListener,
|
||||||
@@ -202,7 +200,6 @@ class HomeActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:LongMethod")
|
|
||||||
private fun handleBottomBar() {
|
private fun handleBottomBar() {
|
||||||
tabNewBadge =
|
tabNewBadge =
|
||||||
TextBadgeItem()
|
TextBadgeItem()
|
||||||
@@ -285,7 +282,7 @@ class HomeActivity :
|
|||||||
|
|
||||||
handleBottomBarActions()
|
handleBottomBarActions()
|
||||||
|
|
||||||
handleGdprDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
|
handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
|
||||||
|
|
||||||
handleRecurringTask()
|
handleRecurringTask()
|
||||||
CountingIdlingResourceSingleton.increment()
|
CountingIdlingResourceSingleton.increment()
|
||||||
@@ -297,10 +294,10 @@ class HomeActivity :
|
|||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleGdprDialog(gdprShown: Boolean) {
|
private fun handleGDPRDialog(GDPRShown: Boolean) {
|
||||||
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
|
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
|
||||||
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
|
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
|
||||||
if (!gdprShown) {
|
if (!GDPRShown) {
|
||||||
val alertDialog = AlertDialog.Builder(this).create()
|
val alertDialog = AlertDialog.Builder(this).create()
|
||||||
alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
|
alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
|
||||||
alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
|
alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
|
||||||
@@ -317,44 +314,50 @@ class HomeActivity :
|
|||||||
|
|
||||||
private fun reloadLayoutManager() {
|
private fun reloadLayoutManager() {
|
||||||
val currentManager = binding.recyclerView.layoutManager
|
val currentManager = binding.recyclerView.layoutManager
|
||||||
|
val layoutManager: RecyclerView.LayoutManager
|
||||||
|
|
||||||
fun gridLayoutManager() {
|
// This will only update the layout manager if settings changed
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
when (currentManager) {
|
when (currentManager) {
|
||||||
is StaggeredGridLayoutManager ->
|
is StaggeredGridLayoutManager ->
|
||||||
if (!appSettingsService.isCardViewEnabled()) {
|
if (!appSettingsService.isCardViewEnabled()) {
|
||||||
gridLayoutManager()
|
layoutManager =
|
||||||
|
GridLayoutManager(
|
||||||
|
this,
|
||||||
|
calculateNoOfColumns(),
|
||||||
|
)
|
||||||
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
}
|
}
|
||||||
|
|
||||||
is GridLayoutManager ->
|
is GridLayoutManager ->
|
||||||
if (appSettingsService.isCardViewEnabled()) {
|
if (appSettingsService.isCardViewEnabled()) {
|
||||||
staggererdGridLayoutManager()
|
layoutManager =
|
||||||
|
StaggeredGridLayoutManager(
|
||||||
|
calculateNoOfColumns(),
|
||||||
|
StaggeredGridLayoutManager.VERTICAL,
|
||||||
|
)
|
||||||
|
layoutManager.gapStrategy =
|
||||||
|
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
|
||||||
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
}
|
}
|
||||||
|
|
||||||
else ->
|
else ->
|
||||||
if (currentManager == null) {
|
if (currentManager == null) {
|
||||||
if (!appSettingsService.isCardViewEnabled()) {
|
if (!appSettingsService.isCardViewEnabled()) {
|
||||||
gridLayoutManager()
|
layoutManager =
|
||||||
|
GridLayoutManager(
|
||||||
|
this,
|
||||||
|
calculateNoOfColumns(),
|
||||||
|
)
|
||||||
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
} else {
|
} 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) {
|
private fun handleListResult(appendResults: Boolean = false) {
|
||||||
val oldManager = binding.recyclerView.layoutManager
|
|
||||||
if (appendResults) {
|
if (appendResults) {
|
||||||
|
val oldManager = binding.recyclerView.layoutManager
|
||||||
firstVisible =
|
firstVisible =
|
||||||
when (oldManager) {
|
when (oldManager) {
|
||||||
is StaggeredGridLayoutManager ->
|
is StaggeredGridLayoutManager ->
|
||||||
@@ -493,13 +496,7 @@ class HomeActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:ComplexCondition")
|
if (recyclerAdapter == null) {
|
||||||
if (recyclerAdapter == null ||
|
|
||||||
(
|
|
||||||
(recyclerAdapter is ItemListAdapter && appSettingsService.isCardViewEnabled()) ||
|
|
||||||
(recyclerAdapter is ItemCardAdapter && !appSettingsService.isCardViewEnabled())
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (appSettingsService.isCardViewEnabled()) {
|
if (appSettingsService.isCardViewEnabled()) {
|
||||||
recyclerAdapter =
|
recyclerAdapter =
|
||||||
ItemCardAdapter(
|
ItemCardAdapter(
|
||||||
@@ -546,7 +543,7 @@ class HomeActivity :
|
|||||||
private fun calculateNoOfColumns(): Int {
|
private fun calculateNoOfColumns(): Int {
|
||||||
val displayMetrics = resources.displayMetrics
|
val displayMetrics = resources.displayMetrics
|
||||||
val dpWidth = displayMetrics.widthPixels / displayMetrics.density
|
val dpWidth = displayMetrics.widthPixels / displayMetrics.density
|
||||||
return (dpWidth / MIN_WIDTH_CARD_DP).toInt()
|
return (dpWidth / 300).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueryTextChange(p0: String?): Boolean {
|
override fun onQueryTextChange(p0: String?): Boolean {
|
||||||
@@ -595,11 +592,10 @@ class HomeActivity :
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:ReturnCount", "detekt:LongMethod")
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.issue_tracker -> {
|
R.id.issue_tracker -> {
|
||||||
baseContext.openUrlInBrowserAsNewTask(AppSettingsService.BUG_URL)
|
baseContext.openUrlInBrowser(AppSettingsService.BUG_URL)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -30,8 +30,6 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
private const val MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED = 3
|
|
||||||
|
|
||||||
class LoginActivity :
|
class LoginActivity :
|
||||||
AppCompatActivity(),
|
AppCompatActivity(),
|
||||||
DIAware {
|
DIAware {
|
||||||
@@ -219,7 +217,7 @@ class LoginActivity :
|
|||||||
cancel = true
|
cancel = true
|
||||||
binding.urlView.error = getString(R.string.login_url_problem)
|
binding.urlView.error = getString(R.string.login_url_problem)
|
||||||
inValidCount++
|
inValidCount++
|
||||||
if (inValidCount == MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED) {
|
if (inValidCount == 3) {
|
||||||
val alertDialog = AlertDialog.Builder(this).create()
|
val alertDialog = AlertDialog.Builder(this).create()
|
||||||
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
||||||
alertDialog.setMessage(getString(R.string.text_wrong_url))
|
alertDialog.setMessage(getString(R.string.text_wrong_url))
|
||||||
|
@@ -62,7 +62,6 @@ class MyApp :
|
|||||||
private val connectivityStatus: ConnectivityStatus by instance()
|
private val connectivityStatus: ConnectivityStatus by instance()
|
||||||
private val driverFactory: DriverFactory by instance()
|
private val driverFactory: DriverFactory by instance()
|
||||||
|
|
||||||
@Suppress("detekt:ForbiddenComment")
|
|
||||||
// TODO: handle with the "previous" way
|
// TODO: handle with the "previous" way
|
||||||
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
||||||
|
|
||||||
|
@@ -53,7 +53,6 @@ class ReaderActivity :
|
|||||||
showMenuItem(false)
|
showMenuItem(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityReaderBinding.inflate(layoutInflater)
|
binding = ActivityReaderBinding.inflate(layoutInflater)
|
||||||
|
@@ -85,7 +85,6 @@ class UpsertSourceActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
private fun handleSpoutsSpinner() {
|
private fun handleSpoutsSpinner() {
|
||||||
val spoutsKV = HashMap<String, String>()
|
val spoutsKV = HashMap<String, String>()
|
||||||
binding.spoutsSpinner.onItemSelectedListener =
|
binding.spoutsSpinner.onItemSelectedListener =
|
||||||
@@ -174,7 +173,6 @@ class UpsertSourceActivity :
|
|||||||
sourceDetailsUnavailable -> {
|
sourceDetailsUnavailable -> {
|
||||||
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val successfullyAddedSource =
|
val successfullyAddedSource =
|
||||||
|
@@ -118,13 +118,13 @@ class ItemCardAdapter(
|
|||||||
binding.itemImage.setImageDrawable(null)
|
binding.itemImage.setImageDrawable(null)
|
||||||
} else {
|
} else {
|
||||||
binding.itemImage.visibility = View.VISIBLE
|
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()) {
|
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||||
binding.sourceImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
binding.sourceImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
||||||
} else {
|
} else {
|
||||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage, appSettingsService)
|
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -65,10 +65,10 @@ class ItemListAdapter(
|
|||||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||||
binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
||||||
} else {
|
} else {
|
||||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage, appSettingsService)
|
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage, appSettingsService)
|
c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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.android.utils.glide.circularDrawable
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
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.getHtmlDecoded
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -37,7 +36,6 @@ class SourcesListAdapter(
|
|||||||
|
|
||||||
override val di: DI by closestDI(app)
|
override val di: DI by closestDI(app)
|
||||||
private val repository: Repository by instance()
|
private val repository: Repository by instance()
|
||||||
private val appSettingsService: AppSettingsService by instance()
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
@@ -84,7 +82,7 @@ class SourcesListAdapter(
|
|||||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||||
binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded())
|
binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded())
|
||||||
} else {
|
} else {
|
||||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage, appSettingsService)
|
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!itm.error.isNullOrBlank()) {
|
if (!itm.error.isNullOrBlank()) {
|
||||||
|
@@ -26,8 +26,6 @@ import org.kodein.di.instance
|
|||||||
import java.util.Timer
|
import java.util.Timer
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
private const val NOTIFICATION_DELAY = 4000L
|
|
||||||
|
|
||||||
class LoadingWorker(
|
class LoadingWorker(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
params: WorkerParameters,
|
params: WorkerParameters,
|
||||||
@@ -63,7 +61,7 @@ class LoadingWorker(
|
|||||||
handleNewItemsNotification(apiItems, notificationManager)
|
handleNewItemsNotification(apiItems, notificationManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
apiItems.map { it.preloadImages(context, appSettingsService) }
|
apiItems.map { it.preloadImages(context) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Result.success()
|
return Result.success()
|
||||||
@@ -108,11 +106,11 @@ class LoadingWorker(
|
|||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
||||||
|
|
||||||
Timer("", false).schedule(NOTIFICATION_DELAY) {
|
Timer("", false).schedule(4000) {
|
||||||
notificationManager.notify(2, newItemsNotification.build())
|
notificationManager.notify(2, newItemsNotification.build())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timer("", false).schedule(NOTIFICATION_DELAY) {
|
Timer("", false).schedule(4000) {
|
||||||
notificationManager.cancel(1)
|
notificationManager.cancel(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,14 +2,18 @@ package bou.amine.apps.readerforselfossv2.android.fragments
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.TypedArray
|
import android.content.res.TypedArray
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.TypedValue
|
||||||
import android.util.TypedValue.DATA_NULL_UNDEFINED
|
import android.util.TypedValue.DATA_NULL_UNDEFINED
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.InflateException
|
import android.view.InflateException
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -19,6 +23,7 @@ import android.webkit.WebView
|
|||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.widget.NestedScrollView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import bou.amine.apps.readerforselfossv2.android.ImageActivity
|
import bou.amine.apps.readerforselfossv2.android.ImageActivity
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
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.toModel
|
||||||
import bou.amine.apps.readerforselfossv2.android.model.toParcelable
|
import bou.amine.apps.readerforselfossv2.android.model.toParcelable
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
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.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.isUrlValid
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.maybeIfContext
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrlInBrowserAsNewTask
|
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.android.utils.shareLink
|
||||||
import bou.amine.apps.readerforselfossv2.model.MercuryModel
|
import bou.amine.apps.readerforselfossv2.model.MercuryModel
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
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.getImages
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
|
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
|
||||||
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
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.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -61,19 +65,11 @@ import java.util.Locale
|
|||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
|
|
||||||
private const val IMAGE_JPG = "image/jpg"
|
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 :
|
class ArticleFragment :
|
||||||
Fragment(),
|
Fragment(),
|
||||||
DIAware {
|
DIAware {
|
||||||
private var colorOnSurface: Int = 0
|
private var fontSize: Int = 16
|
||||||
private var colorSurface: Int = 0
|
|
||||||
private var fontSize: Int = DEFAULT_FONT_SIZE
|
|
||||||
private lateinit var item: SelfossModel.Item
|
private lateinit var item: SelfossModel.Item
|
||||||
private lateinit var url: String
|
private lateinit var url: String
|
||||||
private lateinit var contentText: String
|
private lateinit var contentText: String
|
||||||
@@ -81,7 +77,7 @@ class ArticleFragment :
|
|||||||
private lateinit var contentImage: String
|
private lateinit var contentImage: String
|
||||||
private lateinit var contentTitle: String
|
private lateinit var contentTitle: String
|
||||||
private lateinit var allImages: ArrayList<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 textAlignment: String
|
||||||
private lateinit var binding: FragmentArticleBinding
|
private lateinit var binding: FragmentArticleBinding
|
||||||
|
|
||||||
@@ -92,6 +88,7 @@ class ArticleFragment :
|
|||||||
private var typeface: Typeface? = null
|
private var typeface: Typeface? = null
|
||||||
private var resId: Int = 0
|
private var resId: Int = 0
|
||||||
private var font = ""
|
private var font = ""
|
||||||
|
private var staticBar = false
|
||||||
|
|
||||||
private val mercuryApi: MercuryApi by instance()
|
private val mercuryApi: MercuryApi by instance()
|
||||||
|
|
||||||
@@ -103,7 +100,6 @@ class ArticleFragment :
|
|||||||
item = pi.toModel()
|
item = pi.toModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:LongMethod")
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
@@ -118,9 +114,6 @@ class ArticleFragment :
|
|||||||
e.sendSilentlyWithAcra()
|
e.sendSilentlyWithAcra()
|
||||||
}
|
}
|
||||||
|
|
||||||
colorOnSurface = getColorFromAttr(R.attr.colorOnSurface)
|
|
||||||
colorSurface = getColorFromAttr(R.attr.colorSurface)
|
|
||||||
|
|
||||||
contentText = item.content
|
contentText = item.content
|
||||||
contentTitle = item.title.getHtmlDecoded()
|
contentTitle = item.title.getHtmlDecoded()
|
||||||
contentImage = item.getThumbnail(repository.baseUrl)
|
contentImage = item.getThumbnail(repository.baseUrl)
|
||||||
@@ -134,11 +127,23 @@ class ArticleFragment :
|
|||||||
allImages = item.getImages()
|
allImages = item.getImages()
|
||||||
|
|
||||||
fontSize = appSettingsService.getFontSize()
|
fontSize = appSettingsService.getFontSize()
|
||||||
|
staticBar = appSettingsService.isStaticBarEnabled()
|
||||||
font = appSettingsService.getFont()
|
font = appSettingsService.getFont()
|
||||||
|
|
||||||
refreshAlignment()
|
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
|
binding.source.text = contentSource
|
||||||
if (typeface != null) {
|
if (typeface != null) {
|
||||||
@@ -146,13 +151,28 @@ class ArticleFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleContent()
|
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) {
|
} catch (e: InflateException) {
|
||||||
e.sendSilentlyWithAcraWithName("webview not available")
|
e.sendSilentlyWithAcraWithName("webview not available")
|
||||||
maybeIfContext {
|
try {
|
||||||
AlertDialog
|
AlertDialog
|
||||||
.Builder(it)
|
.Builder(requireContext())
|
||||||
.setMessage(it.getString(R.string.webview_dialog_issue_message))
|
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
||||||
.setTitle(it.getString(R.string.webview_dialog_issue_title))
|
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
||||||
.setPositiveButton(
|
.setPositiveButton(
|
||||||
android.R.string.ok,
|
android.R.string.ok,
|
||||||
) { _, _ ->
|
) { _, _ ->
|
||||||
@@ -160,6 +180,8 @@ class ArticleFragment :
|
|||||||
requireActivity().finish()
|
requireActivity().finish()
|
||||||
}.create()
|
}.create()
|
||||||
.show()
|
.show()
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,84 +203,72 @@ class ArticleFragment :
|
|||||||
|
|
||||||
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
|
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
|
||||||
binding.imageView.visibility = View.VISIBLE
|
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 {
|
} else {
|
||||||
binding.imageView.visibility = View.GONE
|
binding.imageView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleFloatingToolbar() {
|
private fun handleFloatingToolbar(): FloatingToolbar {
|
||||||
fab = binding.speedDial
|
val floatingToolbar: FloatingToolbar = binding.floatingToolbar
|
||||||
fab.mainFabClosedIconColor = colorOnSurface
|
if (appSettingsService.getPublicAccess()) {
|
||||||
fab.mainFabOpenedIconColor = colorOnSurface
|
floatingToolbar.setMenu(R.menu.reader_toolbar_no_read)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
floatingToolbar.attachFab(fab)
|
||||||
|
|
||||||
private fun handleFloatingToolbarActionItems(c: Context) {
|
floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
|
||||||
fab.addHomeMadeActionItem(
|
|
||||||
R.id.share_action,
|
floatingToolbar.setClickListener(
|
||||||
resources.getDrawable(R.drawable.ic_share_white_24dp),
|
object : FloatingToolbar.ItemClickListener {
|
||||||
R.string.reader_action_share,
|
override fun onItemClick(item: MenuItem) {
|
||||||
colorOnSurface,
|
when (item.itemId) {
|
||||||
colorSurface,
|
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
|
||||||
c,
|
R.id.open_action -> requireActivity().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
|
||||||
)
|
R.id.unread_action ->
|
||||||
fab.addHomeMadeActionItem(
|
try {
|
||||||
R.id.open_action,
|
if (this@ArticleFragment.item.unread) {
|
||||||
resources.getDrawable(R.drawable.ic_open_in_browser_white_24dp),
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
R.string.reader_action_open,
|
repository.markAsRead(this@ArticleFragment.item)
|
||||||
colorOnSurface,
|
}
|
||||||
colorSurface,
|
this@ArticleFragment.item.unread = false
|
||||||
c,
|
Toast
|
||||||
)
|
.makeText(
|
||||||
fab.addHomeMadeActionItem(
|
requireContext(),
|
||||||
R.id.unread_action,
|
R.string.marked_as_read,
|
||||||
resources.getDrawable(R.drawable.ic_baseline_white_eye_24dp),
|
Toast.LENGTH_LONG,
|
||||||
R.string.unmark,
|
).show()
|
||||||
colorOnSurface,
|
} else {
|
||||||
colorSurface,
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
c,
|
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() {
|
private fun refreshAlignment() {
|
||||||
@@ -270,7 +280,6 @@ class ArticleFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
private fun getContentFromMercury() {
|
private fun getContentFromMercury() {
|
||||||
binding.progressBar.visibility = View.VISIBLE
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
|
||||||
@@ -309,12 +318,16 @@ class ArticleFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLeadImage(leadImageUrl: String?) {
|
private fun handleLeadImage(lead_image_url: String?) {
|
||||||
if (!leadImageUrl.isNullOrEmpty()) {
|
if (!lead_image_url.isNullOrEmpty() && context != null) {
|
||||||
maybeIfContext {
|
binding.imageView.visibility = View.VISIBLE
|
||||||
binding.imageView.visibility = View.VISIBLE
|
Glide
|
||||||
it.bitmapFitCenter(leadImageUrl, binding.imageView, appSettingsService)
|
.with(requireContext())
|
||||||
}
|
.asBitmap()
|
||||||
|
.load(
|
||||||
|
lead_image_url,
|
||||||
|
).apply(RequestOptions.fitCenterTransform())
|
||||||
|
.into(binding.imageView)
|
||||||
} else {
|
} else {
|
||||||
binding.imageView.visibility = View.GONE
|
binding.imageView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
@@ -328,79 +341,132 @@ class ArticleFragment :
|
|||||||
view: WebView?,
|
view: WebView?,
|
||||||
url: String,
|
url: String,
|
||||||
): Boolean =
|
): Boolean =
|
||||||
if (url.isUrlValid() &&
|
if (context != null &&
|
||||||
|
url.isUrlValid() &&
|
||||||
binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
||||||
) {
|
) {
|
||||||
maybeIfContext { it.openUrlInBrowserAsNewTask(url) }
|
requireContext().openUrlInBrowser(url)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException", "detekt:ReturnCount")
|
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
override fun shouldInterceptRequest(
|
override fun shouldInterceptRequest(
|
||||||
view: WebView,
|
view: WebView,
|
||||||
url: String,
|
url: String,
|
||||||
): WebResourceResponse? {
|
): WebResourceResponse? {
|
||||||
val (mime: String?, compression: Bitmap.CompressFormat) =
|
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||||
if (url
|
if (url.lowercase(Locale.US).contains(".jpg") ||
|
||||||
.lowercase(Locale.US)
|
url
|
||||||
.contains(".jpg") ||
|
.lowercase(Locale.US)
|
||||||
url.lowercase(Locale.US).contains(".jpeg")
|
.contains(".jpeg")
|
||||||
) {
|
) {
|
||||||
Pair(IMAGE_JPG, Bitmap.CompressFormat.JPEG)
|
try {
|
||||||
} else if (url.lowercase(Locale.US).contains(".png")) {
|
val image =
|
||||||
Pair(IMAGE_PNG, Bitmap.CompressFormat.PNG)
|
Glide
|
||||||
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
.with(view)
|
||||||
Pair(IMAGE_WEBP, Bitmap.CompressFormat.WEBP)
|
.asBitmap()
|
||||||
} else {
|
.apply(glideOptions)
|
||||||
return super.shouldInterceptRequest(view, url)
|
.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() {
|
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 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.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 =
|
val colorSurfaceString =
|
||||||
String.format(
|
String.format(
|
||||||
"#%06X",
|
"#%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 =
|
val colorOnSurfaceString =
|
||||||
String.format(
|
String.format(
|
||||||
"#%06X",
|
"#%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 {
|
try {
|
||||||
|
binding.webcontent.settings.useWideViewPort = true
|
||||||
|
binding.webcontent.settings.loadWithOverviewMode = true
|
||||||
|
binding.webcontent.settings.javaScriptEnabled = false
|
||||||
|
|
||||||
|
handleImageLoading()
|
||||||
|
|
||||||
val gestureDetector =
|
val gestureDetector =
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
activity,
|
activity,
|
||||||
@@ -414,50 +480,49 @@ class ArticleFragment :
|
|||||||
event,
|
event,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.webcontent.settings.layoutAlgorithm =
|
||||||
|
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
||||||
} catch (e: IllegalStateException) {
|
} 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.webcontent.settings.layoutAlgorithm =
|
|
||||||
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
|
||||||
|
|
||||||
var baseUrl: String? = null
|
|
||||||
try {
|
try {
|
||||||
val itemUrl = URL(url)
|
var baseUrl: String? = null
|
||||||
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
try {
|
||||||
} catch (e: MalformedURLException) {
|
val itemUrl = URL(url)
|
||||||
e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
|
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
||||||
}
|
} catch (e: MalformedURLException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
|
||||||
|
}
|
||||||
|
|
||||||
val fontName: String =
|
val fontName =
|
||||||
maybeIfContext {
|
|
||||||
when (font) {
|
when (font) {
|
||||||
it.getString(R.string.open_sans_font_id) -> "Open Sans"
|
getString(R.string.open_sans_font_id) -> "Open Sans"
|
||||||
it.getString(R.string.roboto_font_id) -> "Roboto"
|
getString(R.string.roboto_font_id) -> "Roboto"
|
||||||
it.getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
|
getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
}?.toString().orEmpty()
|
|
||||||
|
|
||||||
val fontLinkAndStyle =
|
val fontLinkAndStyle =
|
||||||
if (fontName.isNotEmpty()) {
|
if (font.isNotEmpty()) {
|
||||||
"""<link href="https://fonts.googleapis.com/css?family=${
|
"""<link href="https://fonts.googleapis.com/css?family=${
|
||||||
fontName.replace(
|
fontName.replace(
|
||||||
" ",
|
" ",
|
||||||
"+",
|
"+",
|
||||||
)
|
)
|
||||||
}" rel="stylesheet">
|
}" rel="stylesheet">
|
||||||
|<style>
|
|<style>
|
||||||
| * {
|
| * {
|
||||||
| font-family: '$fontName';
|
| font-family: '$fontName';
|
||||||
| }
|
| }
|
||||||
|</style>
|
|</style>
|
||||||
""".trimMargin()
|
""".trimMargin()
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
binding.webcontent.loadDataWithBaseURL(
|
binding.webcontent.loadDataWithBaseURL(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
"""<html>
|
"""<html>
|
||||||
@@ -474,7 +539,7 @@ class ArticleFragment :
|
|||||||
| color: ${
|
| color: ${
|
||||||
String.format(
|
String.format(
|
||||||
"#%06X",
|
"#%06X",
|
||||||
WHITE_COLOR_HEX and (maybeIfContext { it.resources.getColor(R.color.colorAccent) } as Int),
|
0xFFFFFF and context.resources.getColor(R.color.colorAccent),
|
||||||
)
|
)
|
||||||
} !important;
|
} !important;
|
||||||
| }
|
| }
|
||||||
@@ -531,8 +596,10 @@ class ArticleFragment :
|
|||||||
|
|
||||||
private fun openInBrowserAfterFailing() {
|
private fun openInBrowserAfterFailing() {
|
||||||
binding.progressBar.visibility = View.GONE
|
binding.progressBar.visibility = View.GONE
|
||||||
maybeIfContext {
|
try {
|
||||||
it.openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
|
requireContext().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.fragments
|
package bou.amine.apps.readerforselfossv2.android.fragments
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.GradientDrawable
|
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.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
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.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getColorHexCode
|
import bou.amine.apps.readerforselfossv2.utils.getColorHexCode
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.target.ViewTarget
|
import com.bumptech.glide.request.target.ViewTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
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.android.x.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
private const val DRAWABLE_SIZE = 30
|
|
||||||
|
|
||||||
class FilterSheetFragment :
|
class FilterSheetFragment :
|
||||||
BottomSheetDialogFragment(),
|
BottomSheetDialogFragment(),
|
||||||
DIAware {
|
DIAware {
|
||||||
private lateinit var binding: FilterFragmentBinding
|
private lateinit var binding: FilterFragmentBinding
|
||||||
override val di: DI by closestDI()
|
override val di: DI by closestDI()
|
||||||
private val repository: Repository by instance()
|
private val repository: Repository by instance()
|
||||||
private val appSettingsService: AppSettingsService by instance()
|
|
||||||
|
|
||||||
private var selectedChip: Chip? = null
|
private var selectedChip: Chip? = null
|
||||||
|
|
||||||
@@ -60,8 +56,8 @@ class FilterSheetFragment :
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
handleTagChips()
|
handleTagChips(requireContext())
|
||||||
handleSourceChips()
|
handleSourceChips(requireContext())
|
||||||
|
|
||||||
binding.progressBar2.visibility = GONE
|
binding.progressBar2.visibility = GONE
|
||||||
binding.filterView.visibility = VISIBLE
|
binding.filterView.visibility = VISIBLE
|
||||||
@@ -79,16 +75,17 @@ class FilterSheetFragment :
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleSourceChips() {
|
private suspend fun handleSourceChips(context: Context) {
|
||||||
val sourceGroup = binding.sourcesGroup
|
val sourceGroup = binding.sourcesGroup
|
||||||
|
|
||||||
repository.getSourcesDetailsOrStats().forEachIndexed { _, source ->
|
repository.getSourcesDetailsOrStats().forEachIndexed { _, source ->
|
||||||
val c = Chip(context)
|
val c = Chip(context)
|
||||||
c.ellipsize = TextUtils.TruncateAt.END
|
c.ellipsize = TextUtils.TruncateAt.END
|
||||||
|
|
||||||
maybeIfContext {
|
Glide
|
||||||
it.imageIntoViewTarget(
|
.with(context)
|
||||||
source.getIcon(repository.baseUrl),
|
.load(source.getIcon(repository.baseUrl))
|
||||||
|
.into(
|
||||||
object : ViewTarget<Chip?, Drawable?>(c) {
|
object : ViewTarget<Chip?, Drawable?>(c) {
|
||||||
override fun onResourceReady(
|
override fun onResourceReady(
|
||||||
resource: Drawable,
|
resource: Drawable,
|
||||||
@@ -101,9 +98,7 @@ class FilterSheetFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
appSettingsService,
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
c.text = source.title.getHtmlDecoded()
|
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 tagGroup = binding.tagsGroup
|
||||||
|
|
||||||
val tags = repository.getTags()
|
val tags = repository.getTags()
|
||||||
@@ -161,8 +156,8 @@ class FilterSheetFragment :
|
|||||||
}
|
}
|
||||||
gd.setColor(gdColor)
|
gd.setColor(gdColor)
|
||||||
gd.shape = GradientDrawable.RECTANGLE
|
gd.shape = GradientDrawable.RECTANGLE
|
||||||
gd.setSize(DRAWABLE_SIZE, DRAWABLE_SIZE)
|
gd.setSize(30, 30)
|
||||||
gd.cornerRadius = DRAWABLE_SIZE.toFloat()
|
gd.cornerRadius = 30F
|
||||||
c.chipIcon = gd
|
c.chipIcon = gd
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
|
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
|
||||||
|
@@ -6,19 +6,13 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentImageBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentImageBinding
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapWithCache
|
import com.bumptech.glide.Glide
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import org.kodein.di.DI
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import org.kodein.di.DIAware
|
|
||||||
import org.kodein.di.android.x.closestDI
|
|
||||||
import org.kodein.di.instance
|
|
||||||
|
|
||||||
class ImageFragment :
|
class ImageFragment : Fragment() {
|
||||||
Fragment(),
|
|
||||||
DIAware {
|
|
||||||
override val di: DI by closestDI()
|
|
||||||
private val appSettingsService: AppSettingsService by instance()
|
|
||||||
private lateinit var imageUrl: String
|
private lateinit var imageUrl: String
|
||||||
|
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||||
private var _binding: FragmentImageBinding? = null
|
private var _binding: FragmentImageBinding? = null
|
||||||
val binding get() = _binding
|
val binding get() = _binding
|
||||||
|
|
||||||
@@ -37,7 +31,12 @@ class ImageFragment :
|
|||||||
val view = binding?.root
|
val view = binding?.root
|
||||||
|
|
||||||
binding!!.photoView.visibility = View.VISIBLE
|
binding!!.photoView.visibility = View.VISIBLE
|
||||||
requireActivity().bitmapWithCache(imageUrl, binding!!.photoView, appSettingsService)
|
Glide
|
||||||
|
.with(requireActivity())
|
||||||
|
.asBitmap()
|
||||||
|
.apply(glideOptions)
|
||||||
|
.load(imageUrl)
|
||||||
|
.into(binding!!.photoView)
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
@@ -3,21 +3,26 @@ package bou.amine.apps.readerforselfossv2.android.model
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.webkit.URLUtil
|
import android.webkit.URLUtil
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
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.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getImages
|
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(
|
fun SelfossModel.Item.preloadImages(context: Context): Boolean {
|
||||||
context: Context,
|
|
||||||
appSettingsService: AppSettingsService,
|
|
||||||
): Boolean {
|
|
||||||
val imageUrls = this.getImages()
|
val imageUrls = this.getImages()
|
||||||
|
|
||||||
|
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (url in imageUrls) {
|
for (url in imageUrls) {
|
||||||
if (URLUtil.isValidUrl(url)) {
|
if (URLUtil.isValidUrl(url)) {
|
||||||
context.preloadImage(url, appSettingsService)
|
Glide
|
||||||
|
.with(context)
|
||||||
|
.asBitmap()
|
||||||
|
.apply(glideOptions)
|
||||||
|
.load(url)
|
||||||
|
.submit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
|
@@ -26,10 +26,6 @@ import org.kodein.di.android.closestDI
|
|||||||
|
|
||||||
private const val TITLE_TAG = "settingsActivityTitle"
|
private const val TITLE_TAG = "settingsActivityTitle"
|
||||||
|
|
||||||
const val MAX_ITEMS_NUMBER = 200
|
|
||||||
|
|
||||||
private const val MIN_ITEMS_NUMBER = 1
|
|
||||||
|
|
||||||
class SettingsActivity :
|
class SettingsActivity :
|
||||||
AppCompatActivity(),
|
AppCompatActivity(),
|
||||||
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
|
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
|
||||||
@@ -147,7 +143,7 @@ class SettingsActivity :
|
|||||||
InputFilter { source, _, _, dest, _, _ ->
|
InputFilter { source, _, _, dest, _, _ ->
|
||||||
try {
|
try {
|
||||||
val input: Int = (dest.toString() + source.toString()).toInt()
|
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) {
|
} catch (nfe: NumberFormatException) {
|
||||||
Toast
|
Toast
|
||||||
.makeText(
|
.makeText(
|
||||||
|
@@ -2,12 +2,7 @@ package bou.amine.apps.readerforselfossv2.android.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
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.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
|
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
|
||||||
|
|
||||||
fun Context.shareLink(
|
fun Context.shareLink(
|
||||||
@@ -28,32 +23,3 @@ fun Context.shareLink(
|
|||||||
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
).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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -72,7 +72,6 @@ fun Context.openUrlInBrowser(url: String) {
|
|||||||
this.mayBeStartActivity(intent)
|
this.mayBeStartActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
fun Context.mayBeStartActivity(intent: Intent) {
|
fun Context.mayBeStartActivity(intent: Intent) {
|
||||||
try {
|
try {
|
||||||
this.startActivity(intent)
|
this.startActivity(intent)
|
||||||
|
@@ -1,13 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.utils.bottombar
|
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.ashokvarma.bottomnavigation.TextBadgeItem
|
||||||
import com.leinardi.android.speeddial.SpeedDialActionItem
|
|
||||||
import com.leinardi.android.speeddial.SpeedDialView
|
|
||||||
|
|
||||||
fun TextBadgeItem.removeBadge(): TextBadgeItem {
|
fun TextBadgeItem.removeBadge(): TextBadgeItem {
|
||||||
this.setText("")
|
this.setText("")
|
||||||
@@ -16,25 +9,3 @@ fun TextBadgeItem.removeBadge(): TextBadgeItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun TextBadgeItem.maybeShow(): TextBadgeItem = if (this.isHidden) this.show() else this
|
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(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@@ -2,135 +2,42 @@ package bou.amine.apps.readerforselfossv2.android.utils.glide
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.webkit.WebView
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
|
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.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.RequestOptions
|
||||||
import com.bumptech.glide.request.target.ViewTarget
|
|
||||||
import com.google.android.material.chip.Chip
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
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(
|
fun Context.bitmapCenterCrop(
|
||||||
url: String,
|
url: String,
|
||||||
iv: ImageView,
|
iv: ImageView,
|
||||||
appSettingsService: AppSettingsService,
|
|
||||||
) = Glide
|
) = Glide
|
||||||
.with(this)
|
.with(this)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(url.toGlideUrl(appSettingsService))
|
.load(url)
|
||||||
.apply(RequestOptions.centerCropTransform())
|
.apply(RequestOptions.centerCropTransform())
|
||||||
.into(iv)
|
.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(
|
fun Context.circularDrawable(
|
||||||
url: String,
|
url: String,
|
||||||
view: CircleImageView,
|
view: CircleImageView,
|
||||||
appSettingsService: AppSettingsService,
|
|
||||||
) {
|
) {
|
||||||
view.textView.text = ""
|
view.textView.text = ""
|
||||||
|
|
||||||
Glide
|
Glide
|
||||||
.with(this)
|
.with(this)
|
||||||
.load(url.toGlideUrl(appSettingsService))
|
.load(url)
|
||||||
.into(view.imageView)
|
.into(view.imageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val BITMAP_INPUT_STREAM_COMPRESSION_QUALITY = 80
|
|
||||||
|
|
||||||
fun getBitmapInputStream(
|
fun getBitmapInputStream(
|
||||||
bitmap: Bitmap,
|
bitmap: Bitmap,
|
||||||
compressFormat: Bitmap.CompressFormat,
|
compressFormat: Bitmap.CompressFormat,
|
||||||
): InputStream {
|
): InputStream {
|
||||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||||
bitmap.compress(compressFormat, BITMAP_INPUT_STREAM_COMPRESSION_QUALITY, byteArrayOutputStream)
|
bitmap.compress(compressFormat, 80, byteArrayOutputStream)
|
||||||
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
|
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
|
||||||
return ByteArrayInputStream(bitmapData)
|
return ByteArrayInputStream(bitmapData)
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.android.utils.network
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
|
import android.os.Build
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
|
||||||
lateinit var s: Snackbar
|
lateinit var s: Snackbar
|
||||||
@@ -10,13 +11,19 @@ lateinit var s: Snackbar
|
|||||||
fun isNetworkAccessible(context: Context): Boolean {
|
fun isNetworkAccessible(context: Context): Boolean {
|
||||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
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 {
|
return when {
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
||||||
else -> false
|
else -> false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val network = connectivityManager.activeNetworkInfo ?: return false
|
||||||
|
return network.isConnectedOrConnecting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -71,13 +71,35 @@
|
|||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
<com.leinardi.android.speeddial.SpeedDialView
|
<FrameLayout
|
||||||
android:id="@+id/speedDial"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="start|bottom|end"
|
||||||
app:layout_behavior="@string/speeddial_scrolling_view_snackbar_behavior"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:sdMainFabClosedSrc="@drawable/ic_add_white_24dp" />
|
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
|
<FrameLayout
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
@@ -97,5 +119,4 @@
|
|||||||
android:progressTint="?attr/colorAccent" />
|
android:progressTint="?attr/colorAccent" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
23
androidApp/src/main/res/menu/reader_toolbar.xml
Normal file
23
androidApp/src/main/res/menu/reader_toolbar.xml
Normal 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>
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</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="remove_source">Remove source</string>
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">Linksbündig</string>
|
<string name="reader_text_align_left">Linksbündig</string>
|
||||||
<string name="reader_text_align_justify">Blocksatz</string>
|
<string name="reader_text_align_justify">Blocksatz</string>
|
||||||
<string name="settings_reader_font">Schriftgröße im Lesemodus</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="remove_source">Quelle entfernen</string>
|
||||||
<string name="pref_theme_title">Heller/Dunkler Modus</string>
|
<string name="pref_theme_title">Heller/Dunkler Modus</string>
|
||||||
<string name="mode_dark">Dunkler Modus</string>
|
<string name="mode_dark">Dunkler Modus</string>
|
||||||
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">Alinear a la izquierda</string>
|
<string name="reader_text_align_left">Alinear a la izquierda</string>
|
||||||
<string name="reader_text_align_justify">Justificado</string>
|
<string name="reader_text_align_justify">Justificado</string>
|
||||||
<string name="settings_reader_font">Modo lectura</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="remove_source">Remove source</string>
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">Aligner à gauche</string>
|
<string name="reader_text_align_left">Aligner à gauche</string>
|
||||||
<string name="reader_text_align_justify">Justifier le texte</string>
|
<string name="reader_text_align_justify">Justifier le texte</string>
|
||||||
<string name="settings_reader_font">Police du lecteur d\'articles</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="remove_source">Supprimer la source</string>
|
||||||
<string name="pref_theme_title">Thème Clair/Sombre</string>
|
<string name="pref_theme_title">Thème Clair/Sombre</string>
|
||||||
<string name="mode_dark">Thème sombre</string>
|
<string name="mode_dark">Thème sombre</string>
|
||||||
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">Aliñar á esquerda</string>
|
<string name="reader_text_align_left">Aliñar á esquerda</string>
|
||||||
<string name="reader_text_align_justify">Xustificado</string>
|
<string name="reader_text_align_justify">Xustificado</string>
|
||||||
<string name="settings_reader_font">Modo lector</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="remove_source">Eliminar fonte</string>
|
||||||
<string name="pref_theme_title">Modo Claro/Escuro</string>
|
<string name="pref_theme_title">Modo Claro/Escuro</string>
|
||||||
<string name="mode_dark">Modo escuro</string>
|
<string name="mode_dark">Modo escuro</string>
|
||||||
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</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="remove_source">Remove source</string>
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</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="remove_source">Remove source</string>
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</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="remove_source">Remove source</string>
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
|
@@ -23,7 +23,6 @@
|
|||||||
<string name="wrong_infos">"Controleer de gegevens nogmaals."</string>
|
<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_not_read">"Fout bij markeren als gelezen"</string>
|
||||||
<string name="all_posts_read">"Alle artikelen gemarkeerd 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="addStringNoUrl">"Login om bronnen toe te voegen"</string>
|
||||||
<string name="cant_get_sources">"Kan de lijst met bronnen niet ophalen"</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>
|
<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_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</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="remove_source">Remove source</string>
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
@@ -129,4 +131,5 @@
|
|||||||
<string name="action_about">"Over"</string>
|
<string name="action_about">"Over"</string>
|
||||||
<string name="marked_as_read">"Artikel gelezen"</string>
|
<string name="marked_as_read">"Artikel gelezen"</string>
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
|
<string name="undo_string">"Ongedaan maken"</string>
|
||||||
</resources>
|
</resources>
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</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="remove_source">Remove source</string>
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</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="remove_source">Remove source</string>
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</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="remove_source">Remove source</string>
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</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="remove_source">Remove source</string>
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">左对齐</string>
|
<string name="reader_text_align_left">左对齐</string>
|
||||||
<string name="reader_text_align_justify">左右对齐</string>
|
<string name="reader_text_align_justify">左右对齐</string>
|
||||||
<string name="settings_reader_font">阅读器字体</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="remove_source">删除源</string>
|
||||||
<string name="pref_theme_title">浅色/深色模式</string>
|
<string name="pref_theme_title">浅色/深色模式</string>
|
||||||
<string name="mode_dark">深色模式</string>
|
<string name="mode_dark">深色模式</string>
|
||||||
|
@@ -106,6 +106,9 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</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="remove_source">Remove source</string>
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
|
@@ -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>
|
|
@@ -108,6 +108,9 @@
|
|||||||
<string name="source_code_pro_font_id" translatable="false">source_code_pro_medium</string>
|
<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="open_sans_font_id" translatable="false">open_sans</string>
|
||||||
<string name="roboto_font_id" translatable="false">roboto</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="remove_source">Remove source</string>
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
|
@@ -30,6 +30,14 @@
|
|||||||
android:summaryOn="@string/pref_article_viewer_on"
|
android:summaryOn="@string/pref_article_viewer_on"
|
||||||
android:title="@string/pref_article_viewer_title"
|
android:title="@string/pref_article_viewer_title"
|
||||||
app:iconSpaceReserved="false"/>
|
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
|
<PreferenceCategory
|
||||||
android:title="@string/pref_general_category_displaying">
|
android:title="@string/pref_general_category_displaying">
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
@file:Suppress("detekt:LargeClass")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.tests.repository
|
package bou.amine.apps.readerforselfossv2.tests.repository
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
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 TAGS = "Test, New"
|
||||||
|
|
||||||
private const val NUMBER_ARTICLES = 100
|
private val NUMBER_ARTICLES = 100
|
||||||
private const val NUMBER_UNREAD = 50
|
private val NUMBER_UNREAD = 50
|
||||||
private const val NUMBER_STARRED = 20
|
private val NUMBER_STARRED = 20
|
||||||
|
|
||||||
class RepositoryTest {
|
class RepositoryTest {
|
||||||
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
||||||
|
786
detekt.yml
786
detekt.yml
@@ -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.*'
|
|
@@ -1,4 +0,0 @@
|
|||||||
**v125010111**
|
|
||||||
|
|
||||||
- Debug trying to fix context issues. (#174)
|
|
||||||
- Changelog for v125010031
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -18,7 +18,7 @@ kotlin.code.style=official
|
|||||||
#Android
|
#Android
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
#android.nonTransitiveRClass=true
|
#android.nonTransitiveRClass=true
|
||||||
android.enableJetifier=false
|
android.enableJetifier=true
|
||||||
android.nonTransitiveRClass=false
|
android.nonTransitiveRClass=false
|
||||||
#MPP
|
#MPP
|
||||||
kotlin.mpp.enableCInteropCommonization=true
|
kotlin.mpp.enableCInteropCommonization=true
|
||||||
|
@@ -9,14 +9,12 @@ class NaiveTrustManager : X509TrustManager {
|
|||||||
chain: Array<out X509Certificate>?,
|
chain: Array<out X509Certificate>?,
|
||||||
authType: String?,
|
authType: String?,
|
||||||
) {
|
) {
|
||||||
// Nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkServerTrusted(
|
override fun checkServerTrusted(
|
||||||
chain: Array<out X509Certificate>?,
|
chain: Array<out X509Certificate>?,
|
||||||
authType: String?,
|
authType: String?,
|
||||||
) {
|
) {
|
||||||
// Nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
|
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
|
||||||
|
@@ -3,7 +3,6 @@ package bou.amine.apps.readerforselfossv2.utils
|
|||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
|
|
||||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
|
||||||
actual class DateUtils {
|
actual class DateUtils {
|
||||||
actual companion object {
|
actual companion object {
|
||||||
actual fun parseRelativeDate(dateString: String): String {
|
actual fun parseRelativeDate(dateString: String): String {
|
||||||
|
@@ -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)
|
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> {
|
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
||||||
val allImages = ArrayList<String>()
|
val allImages = ArrayList<String>()
|
||||||
|
|
||||||
for (image in Jsoup.parse(content).getElementsByTag("img")) {
|
for (image in Jsoup.parse(content).getElementsByTag("img")) {
|
||||||
val url = image.attr("src")
|
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)
|
allImages.add(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,16 +1,13 @@
|
|||||||
@file:Suppress("detekt:LongParameterList")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.model
|
package bou.amine.apps.readerforselfossv2.model
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
class MercuryModel {
|
class MercuryModel {
|
||||||
@Suppress("detekt:ConstructorParameterNaming")
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class ParsedContent(
|
class ParsedContent(
|
||||||
val title: String? = null,
|
val title: String? = null,
|
||||||
val content: String? = null,
|
val content: String? = null,
|
||||||
val lead_image_url: String? = null,
|
val lead_image_url: String? = null, // NOSONAR
|
||||||
val url: String? = null,
|
val url: String? = null,
|
||||||
val error: Boolean? = null,
|
val error: Boolean? = null,
|
||||||
val message: String? = null,
|
val message: String? = null,
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
@file:Suppress("detekt:LongParameterList")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.model
|
package bou.amine.apps.readerforselfossv2.model
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
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.int
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
|
||||||
class ModelException(
|
|
||||||
message: String,
|
|
||||||
) : Throwable(message)
|
|
||||||
|
|
||||||
class SelfossModel {
|
class SelfossModel {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Tag(
|
data class Tag(
|
||||||
@@ -147,7 +141,7 @@ class SelfossModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stringUrl.isEmptyOrNullOrNullString()) {
|
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
|
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>> {
|
object TagsListSerializer : KSerializer<List<String>> {
|
||||||
override fun deserialize(decoder: Decoder): List<String> =
|
override fun deserialize(decoder: Decoder): List<String> =
|
||||||
when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {
|
when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
@file:Suppress("detekt:TooManyFunctions")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.repository
|
package bou.amine.apps.readerforselfossv2.repository
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.dao.ACTION
|
import bou.amine.apps.readerforselfossv2.dao.ACTION
|
||||||
@@ -25,8 +23,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
private const val MAX_ITEMS_NUMBER = 200
|
|
||||||
|
|
||||||
class Repository(
|
class Repository(
|
||||||
private val api: SelfossApi,
|
private val api: SelfossApi,
|
||||||
private val appSettingsService: AppSettingsService,
|
private val appSettingsService: AppSettingsService,
|
||||||
@@ -131,7 +127,7 @@ class Repository(
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
MAX_ITEMS_NUMBER,
|
200,
|
||||||
)
|
)
|
||||||
return if (items.success && items.data != null) {
|
return if (items.success && items.data != null) {
|
||||||
items.data
|
items.data
|
||||||
@@ -143,7 +139,6 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:ForbiddenComment")
|
|
||||||
suspend fun reloadBadges(): Boolean {
|
suspend fun reloadBadges(): Boolean {
|
||||||
var success = false
|
var success = false
|
||||||
if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
@@ -564,7 +559,6 @@ class Repository(
|
|||||||
item.id.toString(),
|
item.id.toString(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
|
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
|
||||||
try {
|
try {
|
||||||
val newItems = getMaxItemsForBackground(ItemType.UNREAD)
|
val newItems = getMaxItemsForBackground(ItemType.UNREAD)
|
||||||
|
@@ -33,7 +33,6 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse =
|
|||||||
SuccessResponse(false)
|
SuccessResponse(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
|
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
|
||||||
try {
|
try {
|
||||||
return if (r != null && r.status.isSuccess()) {
|
return if (r != null && r.status.isSuccess()) {
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
@file:Suppress("detekt:TooManyFunctions", "detekt:LongParameterList", "detekt:LargeClass")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.rest
|
package bou.amine.apps.readerforselfossv2.rest
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
@@ -37,8 +35,6 @@ import kotlinx.serialization.json.Json
|
|||||||
|
|
||||||
expect fun setupInsecureHttpEngine(config: CIOEngineConfig)
|
expect fun setupInsecureHttpEngine(config: CIOEngineConfig)
|
||||||
|
|
||||||
private const val VERSION_WHERE_POST_LOGIN_SHOULD_WORK = 5
|
|
||||||
|
|
||||||
class SelfossApi(
|
class SelfossApi(
|
||||||
private val appSettingsService: AppSettingsService,
|
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 =
|
suspend fun logout(): SuccessResponse =
|
||||||
if (shouldHaveNewLogout()) {
|
if (shouldHaveNewLogout()) {
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
@file:Suppress("detekt:TooManyFunctions")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.service
|
package bou.amine.apps.readerforselfossv2.service
|
||||||
|
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
|
@@ -1,19 +1,7 @@
|
|||||||
@file:Suppress("detekt:TooManyFunctions")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.service
|
package bou.amine.apps.readerforselfossv2.service
|
||||||
|
|
||||||
import com.russhwolf.settings.Settings
|
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(
|
class AppSettingsService(
|
||||||
acraSenderServiceProcess: Boolean = false,
|
acraSenderServiceProcess: Boolean = false,
|
||||||
) {
|
) {
|
||||||
@@ -48,11 +36,12 @@ class AppSettingsService(
|
|||||||
private var notifyNewItems: Boolean? = null
|
private var notifyNewItems: Boolean? = null
|
||||||
private var itemsNumber: Int? = null
|
private var itemsNumber: Int? = null
|
||||||
private var apiTimeout: Long? = 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 markOnScroll: Boolean? = null
|
||||||
private var activeAlignment: Int? = null
|
private var activeAlignment: Int? = null
|
||||||
|
|
||||||
private var fontSize: Int? = null
|
private var fontSize: Int? = null
|
||||||
|
private var staticBar: Boolean? = null
|
||||||
private var font: String = ""
|
private var font: String = ""
|
||||||
private var theme: Int? = null
|
private var theme: Int? = null
|
||||||
|
|
||||||
@@ -152,14 +141,13 @@ class AppSettingsService(
|
|||||||
return itemsNumber!!
|
return itemsNumber!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
private fun refreshItemsNumber() {
|
private fun refreshItemsNumber() {
|
||||||
itemsNumber =
|
itemsNumber =
|
||||||
try {
|
try {
|
||||||
settings.getString(API_ITEMS_NUMBER, DEFAULT_ITEMS_NUMBER.toString()).toInt()
|
settings.getString(API_ITEMS_NUMBER, "20").toInt()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
settings.remove(API_ITEMS_NUMBER)
|
settings.remove(API_ITEMS_NUMBER)
|
||||||
DEFAULT_ITEMS_NUMBER
|
20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,24 +158,22 @@ class AppSettingsService(
|
|||||||
return apiTimeout!!
|
return apiTimeout!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:MagicNumber")
|
|
||||||
private fun secToMs(n: Long) = n * 1000
|
private fun secToMs(n: Long) = n * 1000
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
private fun refreshApiTimeout() {
|
private fun refreshApiTimeout() {
|
||||||
apiTimeout =
|
apiTimeout =
|
||||||
secToMs(
|
secToMs(
|
||||||
try {
|
try {
|
||||||
val settingsTimeout = settings.getString(API_TIMEOUT, DEFAULT_API_TIMEOUT.toString())
|
val settingsTimeout = settings.getString(API_TIMEOUT, "60")
|
||||||
if (settingsTimeout.toLong() > 0) {
|
if (settingsTimeout.toLong() > 0) {
|
||||||
settingsTimeout.toLong()
|
settingsTimeout.toLong()
|
||||||
} else {
|
} else {
|
||||||
settings.remove(API_TIMEOUT)
|
settings.remove(API_TIMEOUT)
|
||||||
DEFAULT_API_TIMEOUT
|
60
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
settings.remove(API_TIMEOUT)
|
settings.remove(API_TIMEOUT)
|
||||||
DEFAULT_API_TIMEOUT
|
60
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -301,14 +287,14 @@ class AppSettingsService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshRefreshMinutes() {
|
private fun refreshRefreshMinutes() {
|
||||||
refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, DEFAULT_REFRESH_MINUTES.toString()).toLong()
|
refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, "360").toLong()
|
||||||
if (refreshMinutes <= MIN_REFRESH_MINUTES) {
|
if (refreshMinutes <= 15) {
|
||||||
refreshMinutes = MIN_REFRESH_MINUTES
|
refreshMinutes = 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRefreshMinutes(): Long {
|
fun getRefreshMinutes(): Long {
|
||||||
if (refreshMinutes != DEFAULT_REFRESH_MINUTES) {
|
if (refreshMinutes != 360L) {
|
||||||
refreshRefreshMinutes()
|
refreshRefreshMinutes()
|
||||||
}
|
}
|
||||||
return refreshMinutes
|
return refreshMinutes
|
||||||
@@ -382,7 +368,18 @@ class AppSettingsService(
|
|||||||
if (fontSize != null) {
|
if (fontSize != null) {
|
||||||
refreshFontSize()
|
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() {
|
private fun refreshFont() {
|
||||||
@@ -437,6 +434,7 @@ class AppSettingsService(
|
|||||||
refreshActiveAllignment()
|
refreshActiveAllignment()
|
||||||
refreshFontSize()
|
refreshFontSize()
|
||||||
refreshFont()
|
refreshFont()
|
||||||
|
refreshStaticBarEnabled()
|
||||||
refreshCurrentTheme()
|
refreshCurrentTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,6 +532,8 @@ class AppSettingsService(
|
|||||||
|
|
||||||
const val READER_FONT = "reader_font"
|
const val READER_FONT = "reader_font"
|
||||||
|
|
||||||
|
const val READER_STATIC_BAR = "reader_static_bar"
|
||||||
|
|
||||||
const val READER_FONT_SIZE = "reader_font_size"
|
const val READER_FONT_SIZE = "reader_font_size"
|
||||||
|
|
||||||
const val TEXT_ALIGN = "text_align"
|
const val TEXT_ALIGN = "text_align"
|
||||||
|
@@ -4,12 +4,6 @@ import kotlinx.datetime.LocalDateTime
|
|||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.datetime.toInstant
|
import kotlinx.datetime.toInstant
|
||||||
|
|
||||||
class DateParseException(
|
|
||||||
message: String,
|
|
||||||
e: Throwable? = null,
|
|
||||||
) : Throwable(message, e)
|
|
||||||
|
|
||||||
@Suppress("detekt:ThrowsCount")
|
|
||||||
fun String.toParsedDate(): Long {
|
fun String.toParsedDate(): Long {
|
||||||
// Possible formats are
|
// Possible formats are
|
||||||
// yyyy-mm-dd hh:mm:ss format
|
// yyyy-mm-dd hh:mm:ss format
|
||||||
@@ -27,18 +21,17 @@ fun String.toParsedDate(): Long {
|
|||||||
.find(this)
|
.find(this)
|
||||||
?.groups
|
?.groups
|
||||||
?.get(1)
|
?.get(1)
|
||||||
?.value ?: throw DateParseException("Couldn't parse $this")
|
?.value ?: throw Exception("Couldn't parse $this")
|
||||||
} else {
|
} else {
|
||||||
throw DateParseException("Unrecognized format for $this")
|
throw Exception("Unrecognized format for $this")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} 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()
|
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
|
||||||
expect class DateUtils() {
|
expect class DateUtils() {
|
||||||
companion object {
|
companion object {
|
||||||
fun parseRelativeDate(dateString: String): String
|
fun parseRelativeDate(dateString: String): String
|
||||||
|
@@ -17,7 +17,7 @@ fun SOURCE.toView(): SelfossModel.SourceDetail =
|
|||||||
this.id.toInt(),
|
this.id.toInt(),
|
||||||
this.title,
|
this.title,
|
||||||
null,
|
null,
|
||||||
this.tags.split(","),
|
this.tags?.split(","),
|
||||||
this.spout,
|
this.spout,
|
||||||
this.error,
|
this.error,
|
||||||
this.icon,
|
this.icon,
|
||||||
@@ -74,7 +74,6 @@ fun SelfossModel.Item.toEntity(): ITEM =
|
|||||||
this.author,
|
this.author,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("detekt:MagicNumber")
|
|
||||||
fun SelfossModel.Tag.getColorHexCode(): String =
|
fun SelfossModel.Tag.getColorHexCode(): String =
|
||||||
if (this.color.length == 4) { // #000
|
if (this.color.length == 4) { // #000
|
||||||
val char1 = this.color.get(1)
|
val char1 = this.color.get(1)
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
@Suppress("detekt:MagicNumber")
|
|
||||||
enum class ItemType(
|
enum class ItemType(
|
||||||
val position: Int,
|
val position: Int,
|
||||||
val type: String,
|
val type: String,
|
||||||
|
@@ -2,7 +2,6 @@ package bou.amine.apps.readerforselfossv2.utils
|
|||||||
|
|
||||||
fun String?.isEmptyOrNullOrNullString(): Boolean = this == null || this == "null" || this.isEmpty()
|
fun String?.isEmptyOrNullOrNullString(): Boolean = this == null || this == "null" || this.isEmpty()
|
||||||
|
|
||||||
@Suppress("detekt:MagicNumber")
|
|
||||||
fun String.longHash(): Long {
|
fun String.longHash(): Long {
|
||||||
var h = 98764321261L
|
var h = 98764321261L
|
||||||
val l = this.length
|
val l = this.length
|
||||||
|
@@ -2,6 +2,5 @@ package bou.amine.apps.readerforselfossv2.rest
|
|||||||
|
|
||||||
import io.ktor.client.engine.cio.CIOEngineConfig
|
import io.ktor.client.engine.cio.CIOEngineConfig
|
||||||
|
|
||||||
@Suppress("detekt:EmptyFunctionBlock")
|
|
||||||
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
|
||||||
actual class DateUtils {
|
actual class DateUtils {
|
||||||
actual companion object {
|
actual companion object {
|
||||||
actual fun parseRelativeDate(dateString: String): String {
|
actual fun parseRelativeDate(dateString: String): String {
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
|
||||||
actual class DateUtils actual constructor() {
|
actual class DateUtils actual constructor() {
|
||||||
actual companion object {
|
actual companion object {
|
||||||
actual fun parseRelativeDate(dateString: String): String {
|
actual fun parseRelativeDate(dateString: String): String {
|
@@ -2,6 +2,5 @@ package bou.amine.apps.readerforselfossv2.rest
|
|||||||
|
|
||||||
import io.ktor.client.engine.cio.CIOEngineConfig
|
import io.ktor.client.engine.cio.CIOEngineConfig
|
||||||
|
|
||||||
@Suppress("detekt:EmptyFunctionBlock")
|
|
||||||
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
|
||||||
actual class DateUtils {
|
actual class DateUtils {
|
||||||
actual companion object {
|
actual companion object {
|
||||||
actual fun parseRelativeDate(dateString: String): String {
|
actual fun parseRelativeDate(dateString: String): String {
|
||||||
|
Reference in New Issue
Block a user