Compare commits
2 Commits
v125010291
...
6b5f6cbbe0
Author | SHA1 | Date | |
---|---|---|---|
6b5f6cbbe0 | |||
5035392aff |
@@ -3,34 +3,25 @@ root = true
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
|
||||
[.editorconfig]
|
||||
insert_final_newline = false
|
||||
ij_kotlin_line_break_after_multiline_when_entry = false
|
||||
[{*.kt,*.kts}]
|
||||
|
||||
[*.{kt,kts}]
|
||||
# Disable wildcard imports entirely
|
||||
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||
|
||||
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
|
||||
end_of_line = lf
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
|
||||
ij_kotlin_indent_before_arrow_on_new_line = false
|
||||
ij_kotlin_line_break_after_multiline_when_entry = true
|
||||
ij_kotlin_packages_to_use_import_on_demand = unset
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
ktlint_argument_list_wrapping_ignore_when_parameter_count_greater_or_equal_than = unset
|
||||
ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than = 4
|
||||
ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than = unset
|
||||
ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 1
|
||||
ktlint_code_style = ktlint_official
|
||||
ktlint_enum_entry_name_casing = upper_or_camel_cases
|
||||
ktlint_function_naming_ignore_when_annotated_with = unset
|
||||
ktlint_function_signature_body_expression_wrapping = multiline
|
||||
ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 2
|
||||
ktlint_ignore_back_ticked_identifier = false
|
||||
ktlint_property_naming_constant_naming = screaming_snake_case
|
||||
max_line_length = 140
|
||||
|
||||
[**/build]
|
||||
ktlint = disabled
|
@@ -16,13 +16,13 @@ jobs:
|
||||
java-version: '17'
|
||||
cache: gradle
|
||||
- name: Install klint
|
||||
run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.5.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
|
||||
run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.0.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
|
||||
- name: Install detekt
|
||||
run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.7/detekt-cli-1.23.7.zip && unzip detekt-cli-1.23.7.zip
|
||||
run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.1/detekt-cli-1.23.1.zip && unzip detekt-cli-1.23.1.zip
|
||||
- name: Linting...
|
||||
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build'
|
||||
- name: Detecting...
|
||||
run: ./detekt-cli-1.23.7/bin/detekt-cli -c detekt.yml --excludes '**/shared/build/**/*.kt'
|
||||
run: ./detekt-cli-1.23.1/bin/detekt-cli --all-rules --excludes '**/shared/build/**/*.kt' || true
|
||||
build:
|
||||
needs: Lint
|
||||
uses: ./.gitea/workflows/common_build.yml
|
||||
|
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
|
||||
|
||||
- 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")
|
||||
|
||||
// Themes
|
||||
implementation("com.leinardi.android:speed-dial:3.3.0")
|
||||
implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
|
||||
|
||||
// Pager
|
||||
implementation("me.relex:circleindicator:2.1.6")
|
||||
|
@@ -104,14 +104,12 @@ fun testAddSourceWithUrl(
|
||||
onView(withId(R.id.fab))
|
||||
.perform(click())
|
||||
onView(withId(R.id.nameInput))
|
||||
.perform(click())
|
||||
.perform(typeTextIntoFocusedView(sourceName))
|
||||
.perform(click()).perform(typeTextIntoFocusedView(sourceName))
|
||||
onView(withId(R.id.sourceUri))
|
||||
.perform(click())
|
||||
.perform(typeTextIntoFocusedView(url))
|
||||
onView(withId(R.id.tags))
|
||||
.perform(click())
|
||||
.perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
|
||||
.perform(click()).perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
|
||||
onView(withId(R.id.spoutsSpinner))
|
||||
.perform(click())
|
||||
onData(hasToString("RSS Feed")).perform(click())
|
||||
|
@@ -30,21 +30,28 @@ fun withError(
|
||||
): TypeSafeMatcher<View?> {
|
||||
return object : TypeSafeMatcher<View?>() {
|
||||
override fun matchesSafely(view: View?): Boolean {
|
||||
if (view != null && (view !is EditText || view.error == null)) {
|
||||
if (view == null) {
|
||||
return false
|
||||
}
|
||||
val context = view.context
|
||||
if (view !is EditText) {
|
||||
return false
|
||||
}
|
||||
if (view.error == null) {
|
||||
return false
|
||||
}
|
||||
val context = view!!.context
|
||||
|
||||
return (view as EditText).error.toString() == context.getString(id)
|
||||
return view.error.toString() == context.getString(id)
|
||||
}
|
||||
|
||||
override fun describeTo(description: Description?) {
|
||||
// Nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isPopupWindow(): Matcher<Root> = isPlatformPopup()
|
||||
fun isPopupWindow(): Matcher<Root> {
|
||||
return isPlatformPopup()
|
||||
}
|
||||
|
||||
fun withDrawable(
|
||||
@DrawableRes id: Int,
|
||||
@@ -53,7 +60,6 @@ fun withDrawable(
|
||||
description.appendText("ImageView with drawable same as drawable with id $id")
|
||||
}
|
||||
|
||||
@Suppress("detekt:SwallowedException")
|
||||
override fun matchesSafely(view: View): Boolean {
|
||||
val context = view.context
|
||||
val expectedBitmap = context.getDrawable(id)!!.toBitmap()
|
||||
@@ -67,8 +73,8 @@ fun withDrawable(
|
||||
|
||||
fun hasBottombarItemText(
|
||||
@StringRes id: Int,
|
||||
): Matcher<View>? =
|
||||
allOf(
|
||||
): Matcher<View>? {
|
||||
return allOf(
|
||||
withResourceName("fixed_bottom_navigation_icon"),
|
||||
withParent(
|
||||
allOf(
|
||||
@@ -77,21 +83,23 @@ fun hasBottombarItemText(
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun withSettingsCheckboxWidget(
|
||||
@StringRes id: Int,
|
||||
): Matcher<View>? =
|
||||
allOf(
|
||||
): Matcher<View>? {
|
||||
return allOf(
|
||||
withId(android.R.id.switch_widget),
|
||||
withParent(
|
||||
withSettingsCheckboxFrame(id),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun withSettingsCheckboxFrame(
|
||||
@StringRes id: Int,
|
||||
): Matcher<View>? =
|
||||
allOf(
|
||||
): Matcher<View>? {
|
||||
return allOf(
|
||||
withId(android.R.id.widget_frame),
|
||||
hasSibling(
|
||||
allOf(
|
||||
@@ -102,6 +110,7 @@ fun withSettingsCheckboxFrame(
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun openMenu() {
|
||||
openActionBarOverflowOrOptionsMenu(
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package bou.amine.apps.readerforselfossv2.android
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.IdlingRegistry
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
@@ -25,33 +26,35 @@ class LoginActivityTest {
|
||||
@get:Rule
|
||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||
|
||||
private fun getActivity(): Activity? {
|
||||
var activity: Activity? = null
|
||||
activityRule.scenario.onActivity {
|
||||
activity = it
|
||||
}
|
||||
return activity
|
||||
}
|
||||
|
||||
@Before
|
||||
fun registerIdlingResource() {
|
||||
IdlingRegistry
|
||||
.getInstance()
|
||||
IdlingRegistry.getInstance()
|
||||
.register(CountingIdlingResourceSingleton.countingIdlingResource)
|
||||
}
|
||||
|
||||
@After
|
||||
fun unregisterIdlingResource() {
|
||||
IdlingRegistry
|
||||
.getInstance()
|
||||
IdlingRegistry.getInstance()
|
||||
.unregister(CountingIdlingResourceSingleton.countingIdlingResource)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun viewIsInitialized() {
|
||||
onView(withId(R.id.urlView)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.selfSigned))
|
||||
.check(matches(isDisplayed()))
|
||||
.check(matches(isNotChecked()))
|
||||
onView(withId(R.id.selfSigned)).check(matches(isDisplayed())).check(matches(isNotChecked()))
|
||||
.check(
|
||||
matches(isClickable()),
|
||||
)
|
||||
onView(withId(R.id.withLogin))
|
||||
.check(matches(isDisplayed()))
|
||||
.check(matches(isNotChecked()))
|
||||
.check(
|
||||
onView(withId(R.id.withLogin)).check(matches(isDisplayed()))
|
||||
.check(matches(isNotChecked())).check(
|
||||
matches(isClickable()),
|
||||
)
|
||||
}
|
||||
|
@@ -42,7 +42,6 @@ class SettingsActivityGeneralTest {
|
||||
onView(withText(R.string.pref_header_general)).perform(click())
|
||||
}
|
||||
|
||||
@Suppress("detekt:LongMethod")
|
||||
@Test
|
||||
fun testGeneral() {
|
||||
onView(withText(R.string.pref_api_items_number_title)).check(matches(isDisplayed()))
|
||||
@@ -65,6 +64,19 @@ class SettingsActivityGeneralTest {
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxWidget(R.string.reader_static_bar_title)).check(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(),
|
||||
not(isChecked()),
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||
matches(
|
||||
isEnabled(),
|
||||
),
|
||||
)
|
||||
onView(withText(R.string.pref_general_category_displaying)).check(matches(isDisplayed()))
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).check(
|
||||
matches(
|
||||
@@ -106,7 +118,6 @@ class SettingsActivityGeneralTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("detekt:ForbiddenComment")
|
||||
@Test
|
||||
fun testGeneralActionsNumberItems() {
|
||||
onView(withText(R.string.pref_api_items_number_title)).perform(click())
|
||||
@@ -148,6 +159,19 @@ class SettingsActivityGeneralTest {
|
||||
|
||||
@Test
|
||||
fun testGeneralActionsCheckboxes() {
|
||||
// article viewer settings
|
||||
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||
matches(
|
||||
isEnabled(),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_article_viewer_title)).perform(click())
|
||||
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||
matches(
|
||||
not(isEnabled()),
|
||||
),
|
||||
)
|
||||
|
||||
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(not(isEnabled())))
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).perform(click())
|
||||
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(isEnabled()))
|
||||
|
@@ -42,7 +42,6 @@ class SettingsActivityOfflineTest {
|
||||
onView(withText(R.string.pref_header_offline)).perform(click())
|
||||
}
|
||||
|
||||
@Suppress("detekt:LongMethod")
|
||||
@Test
|
||||
fun testOffline() {
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).check(
|
||||
@@ -108,7 +107,6 @@ class SettingsActivityOfflineTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("detekt:LongMethod")
|
||||
@Test
|
||||
fun testOfflineActions() {
|
||||
onView(withText(R.string.pref_switch_items_caching_off)).check(matches(isDisplayed()))
|
||||
|
@@ -45,7 +45,6 @@ class SourcesActivityTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("detekt:SwallowedException")
|
||||
@Test
|
||||
fun addSourceCheckContent() {
|
||||
testAddSourceWithUrl("https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en", sourceName)
|
||||
|
@@ -31,11 +31,11 @@ import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowserAsNewTask
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||
import bou.amine.apps.readerforselfossv2.utils.Enums.ItemType
|
||||
import com.ashokvarma.bottomnavigation.BottomNavigationBar
|
||||
import com.ashokvarma.bottomnavigation.BottomNavigationItem
|
||||
import com.ashokvarma.bottomnavigation.TextBadgeItem
|
||||
@@ -49,8 +49,6 @@ import org.kodein.di.instance
|
||||
import java.security.MessageDigest
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private const val MIN_WIDTH_CARD_DP = 300
|
||||
|
||||
class HomeActivity :
|
||||
AppCompatActivity(),
|
||||
SearchView.OnQueryTextListener,
|
||||
@@ -202,7 +200,6 @@ class HomeActivity :
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("detekt:LongMethod")
|
||||
private fun handleBottomBar() {
|
||||
tabNewBadge =
|
||||
TextBadgeItem()
|
||||
@@ -285,7 +282,7 @@ class HomeActivity :
|
||||
|
||||
handleBottomBarActions()
|
||||
|
||||
handleGdprDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
|
||||
handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
|
||||
|
||||
handleRecurringTask()
|
||||
CountingIdlingResourceSingleton.increment()
|
||||
@@ -297,10 +294,10 @@ class HomeActivity :
|
||||
getElementsAccordingToTab()
|
||||
}
|
||||
|
||||
private fun handleGdprDialog(gdprShown: Boolean) {
|
||||
private fun handleGDPRDialog(GDPRShown: Boolean) {
|
||||
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
|
||||
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
|
||||
if (!gdprShown) {
|
||||
if (!GDPRShown) {
|
||||
val alertDialog = AlertDialog.Builder(this).create()
|
||||
alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
|
||||
alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
|
||||
@@ -317,44 +314,50 @@ class HomeActivity :
|
||||
|
||||
private fun reloadLayoutManager() {
|
||||
val currentManager = binding.recyclerView.layoutManager
|
||||
val layoutManager: RecyclerView.LayoutManager
|
||||
|
||||
fun gridLayoutManager() {
|
||||
val layoutManager =
|
||||
GridLayoutManager(
|
||||
this,
|
||||
calculateNoOfColumns(),
|
||||
)
|
||||
binding.recyclerView.layoutManager = layoutManager
|
||||
}
|
||||
|
||||
fun staggererdGridLayoutManager() {
|
||||
var layoutManager =
|
||||
StaggeredGridLayoutManager(
|
||||
calculateNoOfColumns(),
|
||||
StaggeredGridLayoutManager.VERTICAL,
|
||||
)
|
||||
layoutManager.gapStrategy =
|
||||
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
|
||||
binding.recyclerView.layoutManager = layoutManager
|
||||
}
|
||||
|
||||
// This will only update the layout manager if settings changed
|
||||
when (currentManager) {
|
||||
is StaggeredGridLayoutManager ->
|
||||
if (!appSettingsService.isCardViewEnabled()) {
|
||||
gridLayoutManager()
|
||||
layoutManager =
|
||||
GridLayoutManager(
|
||||
this,
|
||||
calculateNoOfColumns(),
|
||||
)
|
||||
binding.recyclerView.layoutManager = layoutManager
|
||||
}
|
||||
|
||||
is GridLayoutManager ->
|
||||
if (appSettingsService.isCardViewEnabled()) {
|
||||
staggererdGridLayoutManager()
|
||||
layoutManager =
|
||||
StaggeredGridLayoutManager(
|
||||
calculateNoOfColumns(),
|
||||
StaggeredGridLayoutManager.VERTICAL,
|
||||
)
|
||||
layoutManager.gapStrategy =
|
||||
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
|
||||
binding.recyclerView.layoutManager = layoutManager
|
||||
}
|
||||
|
||||
else ->
|
||||
if (currentManager == null) {
|
||||
if (!appSettingsService.isCardViewEnabled()) {
|
||||
gridLayoutManager()
|
||||
layoutManager =
|
||||
GridLayoutManager(
|
||||
this,
|
||||
calculateNoOfColumns(),
|
||||
)
|
||||
binding.recyclerView.layoutManager = layoutManager
|
||||
} else {
|
||||
staggererdGridLayoutManager()
|
||||
layoutManager =
|
||||
StaggeredGridLayoutManager(
|
||||
calculateNoOfColumns(),
|
||||
StaggeredGridLayoutManager.VERTICAL,
|
||||
)
|
||||
layoutManager.gapStrategy =
|
||||
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
|
||||
binding.recyclerView.layoutManager = layoutManager
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -479,8 +482,8 @@ class HomeActivity :
|
||||
}
|
||||
|
||||
private fun handleListResult(appendResults: Boolean = false) {
|
||||
val oldManager = binding.recyclerView.layoutManager
|
||||
if (appendResults) {
|
||||
val oldManager = binding.recyclerView.layoutManager
|
||||
firstVisible =
|
||||
when (oldManager) {
|
||||
is StaggeredGridLayoutManager ->
|
||||
@@ -493,13 +496,7 @@ class HomeActivity :
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("detekt:ComplexCondition")
|
||||
if (recyclerAdapter == null ||
|
||||
(
|
||||
(recyclerAdapter is ItemListAdapter && appSettingsService.isCardViewEnabled()) ||
|
||||
(recyclerAdapter is ItemCardAdapter && !appSettingsService.isCardViewEnabled())
|
||||
)
|
||||
) {
|
||||
if (recyclerAdapter == null) {
|
||||
if (appSettingsService.isCardViewEnabled()) {
|
||||
recyclerAdapter =
|
||||
ItemCardAdapter(
|
||||
@@ -546,7 +543,7 @@ class HomeActivity :
|
||||
private fun calculateNoOfColumns(): Int {
|
||||
val displayMetrics = resources.displayMetrics
|
||||
val dpWidth = displayMetrics.widthPixels / displayMetrics.density
|
||||
return (dpWidth / MIN_WIDTH_CARD_DP).toInt()
|
||||
return (dpWidth / 300).toInt()
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(p0: String?): Boolean {
|
||||
@@ -595,11 +592,10 @@ class HomeActivity :
|
||||
.show()
|
||||
}
|
||||
|
||||
@Suppress("detekt:ReturnCount", "detekt:LongMethod")
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.issue_tracker -> {
|
||||
baseContext.openUrlInBrowserAsNewTask(AppSettingsService.BUG_URL)
|
||||
baseContext.openUrlInBrowser(AppSettingsService.TRACKER_URL)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@@ -84,9 +84,7 @@ class ImageActivity : AppCompatActivity() {
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private inner class ScreenSlidePagerAdapter(
|
||||
fa: FragmentActivity,
|
||||
) : FragmentStateAdapter(fa) {
|
||||
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
|
||||
override fun getItemCount(): Int = allImages.size
|
||||
|
||||
override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position])
|
||||
|
@@ -30,8 +30,6 @@ import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
private const val MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED = 3
|
||||
|
||||
class LoginActivity :
|
||||
AppCompatActivity(),
|
||||
DIAware {
|
||||
@@ -136,18 +134,9 @@ class LoginActivity :
|
||||
binding.passwordView.error = null
|
||||
|
||||
// Store values at the time of the login attempt.
|
||||
val url =
|
||||
binding.urlView.text
|
||||
.toString()
|
||||
.trim()
|
||||
val login =
|
||||
binding.loginView.text
|
||||
.toString()
|
||||
.trim()
|
||||
val password =
|
||||
binding.passwordView.text
|
||||
.toString()
|
||||
.trim()
|
||||
val url = binding.urlView.text.toString().trim()
|
||||
val login = binding.loginView.text.toString().trim()
|
||||
val password = binding.passwordView.text.toString().trim()
|
||||
|
||||
failInvalidUrl(url)
|
||||
failLoginDetails(password, login)
|
||||
@@ -219,7 +208,7 @@ class LoginActivity :
|
||||
cancel = true
|
||||
binding.urlView.error = getString(R.string.login_url_problem)
|
||||
inValidCount++
|
||||
if (inValidCount == MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED) {
|
||||
if (inValidCount == 3) {
|
||||
val alertDialog = AlertDialog.Builder(this).create()
|
||||
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
||||
alertDialog.setMessage(getString(R.string.text_wrong_url))
|
||||
@@ -284,7 +273,7 @@ class LoginActivity :
|
||||
return when (item.itemId) {
|
||||
R.id.issue_tracker -> {
|
||||
val browserIntent =
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.BUG_URL))
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.TRACKER_URL))
|
||||
startActivity(browserIntent)
|
||||
return true
|
||||
}
|
||||
@@ -294,7 +283,7 @@ class LoginActivity :
|
||||
.withAboutIconShown(true)
|
||||
.withAboutVersionShown(true)
|
||||
.withAboutSpecial2("Bug reports")
|
||||
.withAboutSpecial2Description(AppSettingsService.BUG_URL)
|
||||
.withAboutSpecial2Description(AppSettingsService.TRACKER_URL)
|
||||
.withAboutSpecial1("Project Page")
|
||||
.withAboutSpecial1Description(AppSettingsService.SOURCE_URL)
|
||||
.start(this)
|
||||
|
@@ -62,7 +62,6 @@ class MyApp :
|
||||
private val connectivityStatus: ConnectivityStatus by instance()
|
||||
private val driverFactory: DriverFactory by instance()
|
||||
|
||||
@Suppress("detekt:ForbiddenComment")
|
||||
// TODO: handle with the "previous" way
|
||||
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
||||
|
||||
@@ -161,7 +160,7 @@ class MyApp :
|
||||
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
|
||||
val newItemsChannelmChannel =
|
||||
NotificationChannel(
|
||||
AppSettingsService.NEW_ITEMS_CHANNEL,
|
||||
AppSettingsService.NEW_ITEMS_CHANNEL_ID,
|
||||
newItemsChannelname,
|
||||
newItemsChannelimportance,
|
||||
)
|
||||
|
@@ -22,9 +22,7 @@ import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
class ReaderActivity :
|
||||
AppCompatActivity(),
|
||||
DIAware {
|
||||
class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
private var currentItem: Int = 0
|
||||
|
||||
private lateinit var toolbarMenu: Menu
|
||||
@@ -53,7 +51,6 @@ class ReaderActivity :
|
||||
showMenuItem(false)
|
||||
}
|
||||
|
||||
@Suppress("detekt:SwallowedException")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityReaderBinding.inflate(layoutInflater)
|
||||
@@ -102,9 +99,8 @@ class ReaderActivity :
|
||||
oldInstanceState.clear()
|
||||
}
|
||||
|
||||
private inner class ScreenSlidePagerAdapter(
|
||||
fa: FragmentActivity,
|
||||
) : FragmentStateAdapter(fa) {
|
||||
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) :
|
||||
FragmentStateAdapter(fa) {
|
||||
override fun getItemCount(): Int = allItems.size
|
||||
|
||||
override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position])
|
||||
@@ -113,8 +109,8 @@ class ReaderActivity :
|
||||
override fun onKeyDown(
|
||||
keyCode: Int,
|
||||
event: KeyEvent?,
|
||||
): Boolean =
|
||||
when (keyCode) {
|
||||
): Boolean {
|
||||
return when (keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||
val currentFragment =
|
||||
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
||||
@@ -133,6 +129,7 @@ class ReaderActivity :
|
||||
super.onKeyDown(keyCode, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun alignmentMenu() {
|
||||
val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT
|
||||
|
@@ -18,9 +18,7 @@ import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
class SourcesActivity :
|
||||
AppCompatActivity(),
|
||||
DIAware {
|
||||
class SourcesActivity : AppCompatActivity(), DIAware {
|
||||
private lateinit var binding: ActivitySourcesBinding
|
||||
|
||||
override val di by closestDI()
|
||||
@@ -70,12 +68,11 @@ class SourcesActivity :
|
||||
binding.recyclerView.adapter = mAdapter
|
||||
mAdapter.notifyDataSetChanged()
|
||||
} else {
|
||||
Toast
|
||||
.makeText(
|
||||
this@SourcesActivity,
|
||||
R.string.cant_get_sources,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
Toast.makeText(
|
||||
this@SourcesActivity,
|
||||
R.string.cant_get_sources,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
CountingIdlingResourceSingleton.decrement()
|
||||
}
|
||||
|
@@ -21,9 +21,7 @@ import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
class UpsertSourceActivity :
|
||||
AppCompatActivity(),
|
||||
DIAware {
|
||||
class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
||||
private var existingSource: SelfossModel.SourceDetail? = null
|
||||
private var mSpoutsValue: String? = null
|
||||
|
||||
@@ -85,7 +83,6 @@ class UpsertSourceActivity :
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("detekt:SwallowedException")
|
||||
private fun handleSpoutsSpinner() {
|
||||
val spoutsKV = HashMap<String, String>()
|
||||
binding.spoutsSpinner.onItemSelectedListener =
|
||||
@@ -108,12 +105,11 @@ class UpsertSourceActivity :
|
||||
}
|
||||
|
||||
fun handleSpoutFailure(networkIssue: Boolean = false) {
|
||||
Toast
|
||||
.makeText(
|
||||
this@UpsertSourceActivity,
|
||||
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
Toast.makeText(
|
||||
this@UpsertSourceActivity,
|
||||
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
binding.progress.visibility = View.GONE
|
||||
}
|
||||
|
||||
@@ -174,7 +170,6 @@ class UpsertSourceActivity :
|
||||
sourceDetailsUnavailable -> {
|
||||
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
else -> {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val successfullyAddedSource =
|
||||
@@ -197,12 +192,11 @@ class UpsertSourceActivity :
|
||||
if (successfullyAddedSource) {
|
||||
finish()
|
||||
} else {
|
||||
Toast
|
||||
.makeText(
|
||||
this@UpsertSourceActivity,
|
||||
R.string.cant_create_source,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
Toast.makeText(
|
||||
this@UpsertSourceActivity,
|
||||
R.string.cant_create_source,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -118,18 +118,16 @@ class ItemCardAdapter(
|
||||
binding.itemImage.setImageDrawable(null)
|
||||
} else {
|
||||
binding.itemImage.visibility = View.VISIBLE
|
||||
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage, appSettingsService)
|
||||
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage)
|
||||
}
|
||||
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
binding.sourceImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
||||
} else {
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage, appSettingsService)
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class ViewHolder(
|
||||
val binding: CardItemBinding,
|
||||
) : RecyclerView.ViewHolder(binding.root)
|
||||
inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
}
|
||||
|
@@ -65,15 +65,13 @@ class ItemListAdapter(
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
||||
} else {
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage, appSettingsService)
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||
}
|
||||
} else {
|
||||
c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage, appSettingsService)
|
||||
c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class ViewHolder(
|
||||
val binding: ListItemBinding,
|
||||
) : RecyclerView.ViewHolder(binding.root)
|
||||
inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||
import bou.amine.apps.readerforselfossv2.utils.Enums.ItemType
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@@ -16,7 +16,6 @@ import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBindi
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -30,14 +29,12 @@ import org.kodein.di.instance
|
||||
class SourcesListAdapter(
|
||||
private val app: Activity,
|
||||
private val items: ArrayList<SelfossModel.SourceDetail>,
|
||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(),
|
||||
DIAware {
|
||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
|
||||
private val c: Context = app.baseContext
|
||||
private lateinit var binding: SourceListItemBinding
|
||||
|
||||
override val di: DI by closestDI(app)
|
||||
private val repository: Repository by instance()
|
||||
private val appSettingsService: AppSettingsService by instance()
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
@@ -64,12 +61,11 @@ class SourcesListAdapter(
|
||||
notifyItemRemoved(position)
|
||||
notifyItemRangeChanged(position, itemCount)
|
||||
} else {
|
||||
Toast
|
||||
.makeText(
|
||||
app,
|
||||
R.string.can_delete_source,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
Toast.makeText(
|
||||
app,
|
||||
R.string.can_delete_source,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +80,7 @@ class SourcesListAdapter(
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded())
|
||||
} else {
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage, appSettingsService)
|
||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||
}
|
||||
|
||||
if (!itm.error.isNullOrBlank()) {
|
||||
@@ -103,7 +99,5 @@ class SourcesListAdapter(
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
inner class ViewHolder(
|
||||
val mView: ConstraintLayout,
|
||||
) : RecyclerView.ViewHolder(mView)
|
||||
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView)
|
||||
}
|
||||
|
@@ -26,8 +26,6 @@ import org.kodein.di.instance
|
||||
import java.util.Timer
|
||||
import kotlin.concurrent.schedule
|
||||
|
||||
private const val NOTIFICATION_DELAY = 4000L
|
||||
|
||||
class LoadingWorker(
|
||||
val context: Context,
|
||||
params: WorkerParameters,
|
||||
@@ -63,7 +61,7 @@ class LoadingWorker(
|
||||
handleNewItemsNotification(apiItems, notificationManager)
|
||||
}
|
||||
}
|
||||
apiItems.map { it.preloadImages(context, appSettingsService) }
|
||||
apiItems.map { it.preloadImages(context) }
|
||||
}
|
||||
}
|
||||
return Result.success()
|
||||
@@ -95,7 +93,7 @@ class LoadingWorker(
|
||||
NotificationCompat
|
||||
.Builder(
|
||||
applicationContext,
|
||||
AppSettingsService.NEW_ITEMS_CHANNEL,
|
||||
AppSettingsService.NEW_ITEMS_CHANNEL_ID,
|
||||
).setContentTitle(context.getString(R.string.new_items_notification_title))
|
||||
.setContentText(
|
||||
context.getString(
|
||||
@@ -103,16 +101,16 @@ class LoadingWorker(
|
||||
newSize,
|
||||
),
|
||||
).setPriority(PRIORITY_DEFAULT)
|
||||
.setChannelId(AppSettingsService.NEW_ITEMS_CHANNEL)
|
||||
.setChannelId(AppSettingsService.NEW_ITEMS_CHANNEL_ID)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
||||
|
||||
Timer("", false).schedule(NOTIFICATION_DELAY) {
|
||||
Timer("", false).schedule(4000) {
|
||||
notificationManager.notify(2, newItemsNotification.build())
|
||||
}
|
||||
}
|
||||
Timer("", false).schedule(NOTIFICATION_DELAY) {
|
||||
Timer("", false).schedule(4000) {
|
||||
notificationManager.cancel(1)
|
||||
}
|
||||
}
|
||||
|
@@ -2,14 +2,18 @@ package bou.amine.apps.readerforselfossv2.android.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.content.res.TypedArray
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.util.TypedValue.DATA_NULL_UNDEFINED
|
||||
import android.view.GestureDetector
|
||||
import android.view.InflateException
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -19,6 +23,7 @@ import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import androidx.fragment.app.Fragment
|
||||
import bou.amine.apps.readerforselfossv2.android.ImageActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
@@ -27,15 +32,10 @@ import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem
|
||||
import bou.amine.apps.readerforselfossv2.android.model.toModel
|
||||
import bou.amine.apps.readerforselfossv2.android.model.toParcelable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.addHomeMadeActionItem
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.getColorFromAttr
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapFitCenter
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.getGlideImageForResource
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.isUrlValid
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.maybeIfContext
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrlInBrowserAsNewTask
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowserAsNewTask
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
||||
import bou.amine.apps.readerforselfossv2.model.MercuryModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
@@ -46,7 +46,11 @@ import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getImages
|
||||
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
|
||||
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
||||
import com.leinardi.android.speeddial.SpeedDialView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -61,19 +65,9 @@ import java.util.Locale
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
private const val IMAGE_JPG = "image/jpg"
|
||||
private const val IMAGE_PNG = "image/png"
|
||||
private const val IMAGE_WEBP = "image/webp"
|
||||
|
||||
private const val WHITE_COLOR_HEX = 0xFFFFFF
|
||||
|
||||
private const val DEFAULT_FONT_SIZE = 16
|
||||
|
||||
class ArticleFragment :
|
||||
Fragment(),
|
||||
DIAware {
|
||||
private var colorOnSurface: Int = 0
|
||||
private var colorSurface: Int = 0
|
||||
private var fontSize: Int = DEFAULT_FONT_SIZE
|
||||
class ArticleFragment : Fragment(), DIAware {
|
||||
private var fontSize: Int = 16
|
||||
private lateinit var item: SelfossModel.Item
|
||||
private lateinit var url: String
|
||||
private lateinit var contentText: String
|
||||
@@ -81,7 +75,7 @@ class ArticleFragment :
|
||||
private lateinit var contentImage: String
|
||||
private lateinit var contentTitle: String
|
||||
private lateinit var allImages: ArrayList<String>
|
||||
private lateinit var fab: SpeedDialView
|
||||
private lateinit var fab: FloatingActionButton
|
||||
private lateinit var textAlignment: String
|
||||
private lateinit var binding: FragmentArticleBinding
|
||||
|
||||
@@ -92,6 +86,7 @@ class ArticleFragment :
|
||||
private var typeface: Typeface? = null
|
||||
private var resId: Int = 0
|
||||
private var font = ""
|
||||
private var staticBar = false
|
||||
|
||||
private val mercuryApi: MercuryApi by instance()
|
||||
|
||||
@@ -103,7 +98,6 @@ class ArticleFragment :
|
||||
item = pi.toModel()
|
||||
}
|
||||
|
||||
@Suppress("detekt:LongMethod")
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
@@ -118,9 +112,6 @@ class ArticleFragment :
|
||||
e.sendSilentlyWithAcra()
|
||||
}
|
||||
|
||||
colorOnSurface = getColorFromAttr(R.attr.colorOnSurface)
|
||||
colorSurface = getColorFromAttr(R.attr.colorSurface)
|
||||
|
||||
contentText = item.content
|
||||
contentTitle = item.title.getHtmlDecoded()
|
||||
contentImage = item.getThumbnail(repository.baseUrl)
|
||||
@@ -134,11 +125,23 @@ class ArticleFragment :
|
||||
allImages = item.getImages()
|
||||
|
||||
fontSize = appSettingsService.getFontSize()
|
||||
staticBar = appSettingsService.isStaticBarEnabled()
|
||||
font = appSettingsService.getFont()
|
||||
|
||||
refreshAlignment()
|
||||
|
||||
handleFloatingToolbar()
|
||||
fab = binding.fab
|
||||
|
||||
fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent))
|
||||
|
||||
fab.rippleColor = resources.getColor(R.color.colorAccentDark)
|
||||
|
||||
val floatingToolbar: FloatingToolbar = handleFloatingToolbar()
|
||||
|
||||
if (staticBar) {
|
||||
fab.hide()
|
||||
floatingToolbar.show()
|
||||
}
|
||||
|
||||
binding.source.text = contentSource
|
||||
if (typeface != null) {
|
||||
@@ -146,20 +149,37 @@ class ArticleFragment :
|
||||
}
|
||||
|
||||
handleContent()
|
||||
|
||||
binding.nestedScrollView.setOnScrollChangeListener(
|
||||
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||
if (scrollY > oldScrollY) {
|
||||
floatingToolbar.hide()
|
||||
fab.hide()
|
||||
} else {
|
||||
if (staticBar) {
|
||||
floatingToolbar.show()
|
||||
} else {
|
||||
if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
} catch (e: InflateException) {
|
||||
e.sendSilentlyWithAcraWithName("webview not available")
|
||||
maybeIfContext {
|
||||
AlertDialog
|
||||
.Builder(it)
|
||||
.setMessage(it.getString(R.string.webview_dialog_issue_message))
|
||||
.setTitle(it.getString(R.string.webview_dialog_issue_title))
|
||||
try {
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
||||
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
||||
.setPositiveButton(
|
||||
android.R.string.ok,
|
||||
) { _, _ ->
|
||||
appSettingsService.disableArticleViewer()
|
||||
requireActivity().finish()
|
||||
}.create()
|
||||
}
|
||||
.create()
|
||||
.show()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,84 +201,70 @@ class ArticleFragment :
|
||||
|
||||
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
|
||||
binding.imageView.visibility = View.VISIBLE
|
||||
maybeIfContext { it.bitmapFitCenter(contentImage, binding.imageView, appSettingsService) }
|
||||
Glide
|
||||
.with(requireContext())
|
||||
.asBitmap()
|
||||
.load(contentImage)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(binding.imageView)
|
||||
} else {
|
||||
binding.imageView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFloatingToolbar() {
|
||||
fab = binding.speedDial
|
||||
fab.mainFabClosedIconColor = colorOnSurface
|
||||
fab.mainFabOpenedIconColor = colorOnSurface
|
||||
|
||||
maybeIfContext { handleFloatingToolbarActionItems(it) }
|
||||
|
||||
fab.setOnActionSelectedListener { actionItem ->
|
||||
when (actionItem.id) {
|
||||
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
|
||||
R.id.open_action -> requireActivity().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
|
||||
R.id.unread_action ->
|
||||
if (this@ArticleFragment.item.unread) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.markAsRead(this@ArticleFragment.item)
|
||||
}
|
||||
this@ArticleFragment.item.unread = false
|
||||
maybeIfContext {
|
||||
Toast
|
||||
.makeText(
|
||||
it,
|
||||
R.string.marked_as_read,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
}
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.unmarkAsRead(this@ArticleFragment.item)
|
||||
}
|
||||
this@ArticleFragment.item.unread = true
|
||||
maybeIfContext {
|
||||
Toast
|
||||
.makeText(
|
||||
it,
|
||||
R.string.marked_as_unread,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
false
|
||||
private fun handleFloatingToolbar(): FloatingToolbar {
|
||||
val floatingToolbar: FloatingToolbar = binding.floatingToolbar
|
||||
if (appSettingsService.getPublicAccess()) {
|
||||
floatingToolbar.setMenu(R.menu.reader_toolbar_no_read)
|
||||
}
|
||||
}
|
||||
floatingToolbar.attachFab(fab)
|
||||
|
||||
private fun handleFloatingToolbarActionItems(c: Context) {
|
||||
fab.addHomeMadeActionItem(
|
||||
R.id.share_action,
|
||||
resources.getDrawable(R.drawable.ic_share_white_24dp),
|
||||
R.string.reader_action_share,
|
||||
colorOnSurface,
|
||||
colorSurface,
|
||||
c,
|
||||
)
|
||||
fab.addHomeMadeActionItem(
|
||||
R.id.open_action,
|
||||
resources.getDrawable(R.drawable.ic_open_in_browser_white_24dp),
|
||||
R.string.reader_action_open,
|
||||
colorOnSurface,
|
||||
colorSurface,
|
||||
c,
|
||||
)
|
||||
fab.addHomeMadeActionItem(
|
||||
R.id.unread_action,
|
||||
resources.getDrawable(R.drawable.ic_baseline_white_eye_24dp),
|
||||
R.string.unmark,
|
||||
colorOnSurface,
|
||||
colorSurface,
|
||||
c,
|
||||
floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
|
||||
|
||||
floatingToolbar.setClickListener(
|
||||
object : FloatingToolbar.ItemClickListener {
|
||||
override fun onItemClick(item: MenuItem) {
|
||||
when (item.itemId) {
|
||||
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
|
||||
R.id.open_action -> requireActivity().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
|
||||
R.id.unread_action ->
|
||||
try {
|
||||
if (this@ArticleFragment.item.unread) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.markAsRead(this@ArticleFragment.item)
|
||||
}
|
||||
this@ArticleFragment.item.unread = false
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.marked_as_read,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.unmarkAsRead(this@ArticleFragment.item)
|
||||
}
|
||||
this@ArticleFragment.item.unread = true
|
||||
Toast.makeText(
|
||||
context,
|
||||
R.string.marked_as_unread,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
}
|
||||
} catch (e: IllegalStateException) {
|
||||
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemLongClick(item: MenuItem?) {
|
||||
// We do nothing
|
||||
}
|
||||
},
|
||||
)
|
||||
return floatingToolbar
|
||||
}
|
||||
|
||||
private fun refreshAlignment() {
|
||||
@@ -270,7 +276,6 @@ class ArticleFragment :
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("detekt:SwallowedException")
|
||||
private fun getContentFromMercury() {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
|
||||
@@ -309,12 +314,17 @@ class ArticleFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLeadImage(leadImageUrl: String?) {
|
||||
if (!leadImageUrl.isNullOrEmpty()) {
|
||||
maybeIfContext {
|
||||
binding.imageView.visibility = View.VISIBLE
|
||||
it.bitmapFitCenter(leadImageUrl, binding.imageView, appSettingsService)
|
||||
}
|
||||
private fun handleLeadImage(lead_image_url: String?) {
|
||||
if (!lead_image_url.isNullOrEmpty() && context != null) {
|
||||
binding.imageView.visibility = View.VISIBLE
|
||||
Glide
|
||||
.with(requireContext())
|
||||
.asBitmap()
|
||||
.load(
|
||||
lead_image_url,
|
||||
)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(binding.imageView)
|
||||
} else {
|
||||
binding.imageView.visibility = View.GONE
|
||||
}
|
||||
@@ -327,85 +337,125 @@ class ArticleFragment :
|
||||
override fun shouldOverrideUrlLoading(
|
||||
view: WebView?,
|
||||
url: String,
|
||||
): Boolean =
|
||||
if (url.isUrlValid() &&
|
||||
): Boolean {
|
||||
return if (context != null &&
|
||||
url.isUrlValid() &&
|
||||
binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
||||
) {
|
||||
maybeIfContext { it.openUrlInBrowserAsNewTask(url) }
|
||||
requireContext().openUrlInBrowser(url)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("detekt:SwallowedException", "detekt:ReturnCount")
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun shouldInterceptRequest(
|
||||
view: WebView,
|
||||
url: String,
|
||||
): WebResourceResponse? {
|
||||
val (mime: String?, compression: Bitmap.CompressFormat) =
|
||||
if (url
|
||||
.lowercase(Locale.US)
|
||||
.contains(".jpg") ||
|
||||
url.lowercase(Locale.US).contains(".jpeg")
|
||||
) {
|
||||
Pair(IMAGE_JPG, Bitmap.CompressFormat.JPEG)
|
||||
} else if (url.lowercase(Locale.US).contains(".png")) {
|
||||
Pair(IMAGE_PNG, Bitmap.CompressFormat.PNG)
|
||||
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
||||
Pair(IMAGE_WEBP, Bitmap.CompressFormat.WEBP)
|
||||
} else {
|
||||
return super.shouldInterceptRequest(view, url)
|
||||
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||
if (url.lowercase(Locale.US).contains(".jpg") ||
|
||||
url.lowercase(Locale.US)
|
||||
.contains(".jpeg")
|
||||
) {
|
||||
try {
|
||||
val image =
|
||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
||||
.get()
|
||||
return WebResourceResponse(
|
||||
IMAGE_JPG,
|
||||
"UTF-8",
|
||||
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG),
|
||||
)
|
||||
} catch (e: ExecutionException) {
|
||||
// Do nothing
|
||||
}
|
||||
} else if (url.lowercase(Locale.US).contains(".png")) {
|
||||
try {
|
||||
val image =
|
||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
||||
.get()
|
||||
return WebResourceResponse(
|
||||
IMAGE_JPG,
|
||||
"UTF-8",
|
||||
getBitmapInputStream(image, Bitmap.CompressFormat.PNG),
|
||||
)
|
||||
} catch (e: ExecutionException) {
|
||||
// Do nothing
|
||||
}
|
||||
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
||||
try {
|
||||
val image =
|
||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
||||
.get()
|
||||
return WebResourceResponse(
|
||||
IMAGE_JPG,
|
||||
"UTF-8",
|
||||
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP),
|
||||
)
|
||||
} catch (e: ExecutionException) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
try {
|
||||
val image = view.getGlideImageForResource(url, appSettingsService)
|
||||
return WebResourceResponse(
|
||||
mime,
|
||||
"UTF-8",
|
||||
getBitmapInputStream(image, compression),
|
||||
)
|
||||
} catch (e: ExecutionException) {
|
||||
return super.shouldInterceptRequest(view, url)
|
||||
}
|
||||
|
||||
return super.shouldInterceptRequest(view, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("detekt:LongMethod", "detekt:ImplicitDefaultLocale")
|
||||
private fun htmlToWebview() {
|
||||
maybeIfContext {
|
||||
val context: Context
|
||||
try {
|
||||
context = requireContext()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||
return
|
||||
}
|
||||
|
||||
val colorOnSurface = TypedValue()
|
||||
val colorSurface = TypedValue()
|
||||
|
||||
try {
|
||||
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
|
||||
val a: TypedArray = it.obtainStyledAttributes(resId, attrs)
|
||||
val a: TypedArray = context.obtainStyledAttributes(resId, attrs)
|
||||
|
||||
binding.webcontent.settings.standardFontFamily = a.getString(0)
|
||||
""
|
||||
binding.webcontent.visibility = View.VISIBLE
|
||||
|
||||
context.theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
|
||||
|
||||
context.theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
|
||||
} catch (e: IllegalStateException) {
|
||||
e.sendSilentlyWithAcraWithName("Context issue when setting attributes, but context wasn't null before")
|
||||
}
|
||||
binding.webcontent.visibility = View.VISIBLE
|
||||
|
||||
val colorSurfaceString =
|
||||
String.format(
|
||||
"#%06X",
|
||||
WHITE_COLOR_HEX and (if (colorSurface != DATA_NULL_UNDEFINED) colorSurface else WHITE_COLOR_HEX),
|
||||
0xFFFFFF and (if (colorSurface.data != DATA_NULL_UNDEFINED) colorSurface.data else 0xFFFFFF),
|
||||
)
|
||||
|
||||
val colorOnSurfaceString =
|
||||
String.format(
|
||||
"#%06X",
|
||||
WHITE_COLOR_HEX and (if (colorOnSurface != DATA_NULL_UNDEFINED) colorOnSurface else 0),
|
||||
0xFFFFFF and (if (colorOnSurface.data != DATA_NULL_UNDEFINED) colorOnSurface.data else 0),
|
||||
)
|
||||
|
||||
binding.webcontent.settings.useWideViewPort = true
|
||||
binding.webcontent.settings.loadWithOverviewMode = true
|
||||
binding.webcontent.settings.javaScriptEnabled = false
|
||||
|
||||
handleImageLoading()
|
||||
try {
|
||||
binding.webcontent.settings.useWideViewPort = true
|
||||
binding.webcontent.settings.loadWithOverviewMode = true
|
||||
binding.webcontent.settings.javaScriptEnabled = false
|
||||
|
||||
handleImageLoading()
|
||||
|
||||
val gestureDetector =
|
||||
GestureDetector(
|
||||
activity,
|
||||
object : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onSingleTapUp(e: MotionEvent): Boolean = performClick()
|
||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||
return performClick()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -414,50 +464,49 @@ class ArticleFragment :
|
||||
event,
|
||||
)
|
||||
}
|
||||
|
||||
binding.webcontent.settings.layoutAlgorithm =
|
||||
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
||||
} catch (e: IllegalStateException) {
|
||||
e.sendSilentlyWithAcraWithName("Gesture detector issue ?")
|
||||
e.sendSilentlyWithAcraWithName("Context is null but wasn't, and that's causing issues with webview config")
|
||||
return
|
||||
}
|
||||
|
||||
binding.webcontent.settings.layoutAlgorithm =
|
||||
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
||||
|
||||
var baseUrl: String? = null
|
||||
try {
|
||||
val itemUrl = URL(url)
|
||||
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
||||
} catch (e: MalformedURLException) {
|
||||
e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
|
||||
}
|
||||
var baseUrl: String? = null
|
||||
try {
|
||||
val itemUrl = URL(url)
|
||||
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
||||
} catch (e: MalformedURLException) {
|
||||
e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
|
||||
}
|
||||
|
||||
val fontName: String =
|
||||
maybeIfContext {
|
||||
val fontName =
|
||||
when (font) {
|
||||
it.getString(R.string.open_sans_font_id) -> "Open Sans"
|
||||
it.getString(R.string.roboto_font_id) -> "Roboto"
|
||||
it.getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
|
||||
getString(R.string.open_sans_font_id) -> "Open Sans"
|
||||
getString(R.string.roboto_font_id) -> "Roboto"
|
||||
getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
|
||||
else -> ""
|
||||
}
|
||||
}?.toString().orEmpty()
|
||||
|
||||
val fontLinkAndStyle =
|
||||
if (fontName.isNotEmpty()) {
|
||||
"""<link href="https://fonts.googleapis.com/css?family=${
|
||||
fontName.replace(
|
||||
" ",
|
||||
"+",
|
||||
)
|
||||
}" rel="stylesheet">
|
||||
val fontLinkAndStyle =
|
||||
if (font.isNotEmpty()) {
|
||||
"""<link href="https://fonts.googleapis.com/css?family=${
|
||||
fontName.replace(
|
||||
" ",
|
||||
"+",
|
||||
)
|
||||
}" rel="stylesheet">
|
||||
|<style>
|
||||
| * {
|
||||
| font-family: '$fontName';
|
||||
| }
|
||||
|</style>
|
||||
""".trimMargin()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
try {
|
||||
""".trimMargin()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
binding.webcontent.loadDataWithBaseURL(
|
||||
baseUrl,
|
||||
"""<html>
|
||||
@@ -474,7 +523,7 @@ class ArticleFragment :
|
||||
| color: ${
|
||||
String.format(
|
||||
"#%06X",
|
||||
WHITE_COLOR_HEX and (maybeIfContext { it.resources.getColor(R.color.colorAccent) } as Int),
|
||||
0xFFFFFF and context.resources.getColor(R.color.colorAccent),
|
||||
)
|
||||
} !important;
|
||||
| }
|
||||
@@ -531,8 +580,10 @@ class ArticleFragment :
|
||||
|
||||
private fun openInBrowserAfterFailing() {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
maybeIfContext {
|
||||
it.openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
|
||||
try {
|
||||
requireContext().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
|
||||
} catch (e: IllegalStateException) {
|
||||
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,8 +600,7 @@ class ArticleFragment :
|
||||
}
|
||||
|
||||
fun performClick(): Boolean {
|
||||
if (allImages != null &&
|
||||
(
|
||||
if (allImages != null && (
|
||||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
|
||||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
||||
)
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
@@ -15,13 +16,11 @@ import bou.amine.apps.readerforselfossv2.android.HomeActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.imageIntoViewTarget
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.maybeIfContext
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.getColorHexCode
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.ViewTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
@@ -34,15 +33,10 @@ import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.x.closestDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
private const val DRAWABLE_SIZE = 30
|
||||
|
||||
class FilterSheetFragment :
|
||||
BottomSheetDialogFragment(),
|
||||
DIAware {
|
||||
class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
||||
private lateinit var binding: FilterFragmentBinding
|
||||
override val di: DI by closestDI()
|
||||
private val repository: Repository by instance()
|
||||
private val appSettingsService: AppSettingsService by instance()
|
||||
|
||||
private var selectedChip: Chip? = null
|
||||
|
||||
@@ -60,8 +54,8 @@ class FilterSheetFragment :
|
||||
|
||||
try {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
handleTagChips()
|
||||
handleSourceChips()
|
||||
handleTagChips(requireContext())
|
||||
handleSourceChips(requireContext())
|
||||
|
||||
binding.progressBar2.visibility = GONE
|
||||
binding.filterView.visibility = VISIBLE
|
||||
@@ -79,16 +73,16 @@ class FilterSheetFragment :
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private suspend fun handleSourceChips() {
|
||||
private suspend fun handleSourceChips(context: Context) {
|
||||
val sourceGroup = binding.sourcesGroup
|
||||
|
||||
repository.getSourcesDetailsOrStats().forEachIndexed { _, source ->
|
||||
val c = Chip(context)
|
||||
c.ellipsize = TextUtils.TruncateAt.END
|
||||
|
||||
maybeIfContext {
|
||||
it.imageIntoViewTarget(
|
||||
source.getIcon(repository.baseUrl),
|
||||
Glide.with(context)
|
||||
.load(source.getIcon(repository.baseUrl))
|
||||
.into(
|
||||
object : ViewTarget<Chip?, Drawable?>(c) {
|
||||
override fun onResourceReady(
|
||||
resource: Drawable,
|
||||
@@ -101,9 +95,7 @@ class FilterSheetFragment :
|
||||
}
|
||||
}
|
||||
},
|
||||
appSettingsService,
|
||||
)
|
||||
}
|
||||
|
||||
c.text = source.title.getHtmlDecoded()
|
||||
|
||||
@@ -139,7 +131,7 @@ class FilterSheetFragment :
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleTagChips() {
|
||||
private suspend fun handleTagChips(context: Context) {
|
||||
val tagGroup = binding.tagsGroup
|
||||
|
||||
val tags = repository.getTags()
|
||||
@@ -161,8 +153,8 @@ class FilterSheetFragment :
|
||||
}
|
||||
gd.setColor(gdColor)
|
||||
gd.shape = GradientDrawable.RECTANGLE
|
||||
gd.setSize(DRAWABLE_SIZE, DRAWABLE_SIZE)
|
||||
gd.cornerRadius = DRAWABLE_SIZE.toFloat()
|
||||
gd.setSize(30, 30)
|
||||
gd.cornerRadius = 30F
|
||||
c.chipIcon = gd
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
|
||||
|
@@ -6,21 +6,15 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentImageBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapWithCache
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.x.closestDI
|
||||
import org.kodein.di.instance
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
|
||||
class ImageFragment :
|
||||
Fragment(),
|
||||
DIAware {
|
||||
override val di: DI by closestDI()
|
||||
private val appSettingsService: AppSettingsService by instance()
|
||||
class ImageFragment : Fragment() {
|
||||
private lateinit var imageUrl: String
|
||||
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||
private var _binding: FragmentImageBinding? = null
|
||||
val binding get() = _binding
|
||||
private val binding get() = _binding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -37,7 +31,11 @@ class ImageFragment :
|
||||
val view = binding?.root
|
||||
|
||||
binding!!.photoView.visibility = View.VISIBLE
|
||||
requireActivity().bitmapWithCache(imageUrl, binding!!.photoView, appSettingsService)
|
||||
Glide.with(requireActivity())
|
||||
.asBitmap()
|
||||
.apply(glideOptions)
|
||||
.load(imageUrl)
|
||||
.into(binding!!.photoView)
|
||||
|
||||
return view
|
||||
}
|
||||
|
@@ -3,21 +3,23 @@ package bou.amine.apps.readerforselfossv2.android.model
|
||||
import android.content.Context
|
||||
import android.webkit.URLUtil
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.preloadImage
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.getImages
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
|
||||
fun SelfossModel.Item.preloadImages(
|
||||
context: Context,
|
||||
appSettingsService: AppSettingsService,
|
||||
): Boolean {
|
||||
fun SelfossModel.Item.preloadImages(context: Context): Boolean {
|
||||
val imageUrls = this.getImages()
|
||||
|
||||
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000)
|
||||
|
||||
try {
|
||||
for (url in imageUrls) {
|
||||
if (URLUtil.isValidUrl(url)) {
|
||||
context.preloadImage(url, appSettingsService)
|
||||
Glide.with(context).asBitmap()
|
||||
.apply(glideOptions)
|
||||
.load(url).submit()
|
||||
}
|
||||
}
|
||||
} catch (e: Error) {
|
||||
|
@@ -26,10 +26,6 @@ import org.kodein.di.android.closestDI
|
||||
|
||||
private const val TITLE_TAG = "settingsActivityTitle"
|
||||
|
||||
const val MAX_ITEMS_NUMBER = 200
|
||||
|
||||
private const val MIN_ITEMS_NUMBER = 1
|
||||
|
||||
class SettingsActivity :
|
||||
AppCompatActivity(),
|
||||
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
|
||||
@@ -147,7 +143,7 @@ class SettingsActivity :
|
||||
InputFilter { source, _, _, dest, _, _ ->
|
||||
try {
|
||||
val input: Int = (dest.toString() + source.toString()).toInt()
|
||||
if (input in MIN_ITEMS_NUMBER..MAX_ITEMS_NUMBER) return@InputFilter null
|
||||
if (input in 1..200) return@InputFilter null
|
||||
} catch (nfe: NumberFormatException) {
|
||||
Toast
|
||||
.makeText(
|
||||
@@ -240,7 +236,7 @@ class SettingsActivity :
|
||||
|
||||
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
openUrl(AppSettingsService.BUG_URL)
|
||||
openUrl(AppSettingsService.TRACKER_URL)
|
||||
true
|
||||
}
|
||||
|
||||
|
@@ -2,12 +2,7 @@ package bou.amine.apps.readerforselfossv2.android.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.TypedValue
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.fragment.app.Fragment
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
|
||||
|
||||
fun Context.shareLink(
|
||||
@@ -21,39 +16,9 @@ fun Context.shareLink(
|
||||
sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle)
|
||||
sendIntent.type = "text/plain"
|
||||
startActivity(
|
||||
Intent
|
||||
.createChooser(
|
||||
sendIntent,
|
||||
getString(R.string.share),
|
||||
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
Intent.createChooser(
|
||||
sendIntent,
|
||||
getString(R.string.share),
|
||||
).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
|
||||
}
|
||||
}
|
||||
|
@@ -59,5 +59,7 @@ class CircleImageView
|
||||
textView.text = text.toTextDrawableString()
|
||||
}
|
||||
|
||||
private fun colorFromIdentifier(key: String): Int = colorScheme[abs(key.hashCode()) % colorScheme.size]
|
||||
private fun colorFromIdentifier(key: String): Int {
|
||||
return colorScheme[abs(key.hashCode()) % colorScheme.size]
|
||||
}
|
||||
}
|
||||
|
@@ -25,12 +25,11 @@ fun Context.openItemUrl(
|
||||
app: Activity,
|
||||
) {
|
||||
if (!linkDecoded.isUrlValid()) {
|
||||
Toast
|
||||
.makeText(
|
||||
this,
|
||||
this.getString(R.string.cant_open_invalid_url),
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
Toast.makeText(
|
||||
this,
|
||||
this.getString(R.string.cant_open_invalid_url),
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
} else {
|
||||
if (articleViewer) {
|
||||
val intent = Intent(this, ReaderActivity::class.java)
|
||||
@@ -72,7 +71,6 @@ fun Context.openUrlInBrowser(url: String) {
|
||||
this.mayBeStartActivity(intent)
|
||||
}
|
||||
|
||||
@Suppress("detekt:SwallowedException")
|
||||
fun Context.mayBeStartActivity(intent: Intent) {
|
||||
try {
|
||||
this.startActivity(intent)
|
||||
|
@@ -1,13 +1,6 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.utils.bottombar
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.StringRes
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import com.ashokvarma.bottomnavigation.TextBadgeItem
|
||||
import com.leinardi.android.speeddial.SpeedDialActionItem
|
||||
import com.leinardi.android.speeddial.SpeedDialView
|
||||
|
||||
fun TextBadgeItem.removeBadge(): TextBadgeItem {
|
||||
this.setText("")
|
||||
@@ -16,25 +9,3 @@ fun TextBadgeItem.removeBadge(): TextBadgeItem {
|
||||
}
|
||||
|
||||
fun TextBadgeItem.maybeShow(): TextBadgeItem = if (this.isHidden) this.show() else this
|
||||
|
||||
@Suppress("detekt:LongParameterList")
|
||||
fun SpeedDialView.addHomeMadeActionItem(
|
||||
@IdRes actionId: Int,
|
||||
actionIcon: Drawable,
|
||||
@StringRes labelId: Int,
|
||||
colorOnSurface: Int,
|
||||
colorSurface: Int,
|
||||
context: Context,
|
||||
) {
|
||||
this.addActionItem(
|
||||
SpeedDialActionItem
|
||||
.Builder(actionId, actionIcon)
|
||||
.setFabBackgroundColor(context.resources.getColor(R.color.colorAccent))
|
||||
.setFabImageTintColor(colorOnSurface)
|
||||
.setLabel(context.getString(labelId))
|
||||
.setLabelClickable(false)
|
||||
.setLabelBackgroundColor(colorOnSurface)
|
||||
.setLabelColor(colorSurface)
|
||||
.create(),
|
||||
)
|
||||
}
|
||||
|
@@ -2,135 +2,40 @@ package bou.amine.apps.readerforselfossv2.android.utils.glide
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.webkit.WebView
|
||||
import android.widget.ImageView
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.load.model.LazyHeaders
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.ViewTarget
|
||||
import com.google.android.material.chip.Chip
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import kotlin.io.encoding.Base64
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
private const val PRELOAD_IMAGE_TIMEOUT = 10000
|
||||
|
||||
@Suppress("detekt:ReturnCount")
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
fun String.toGlideUrl(appSettingsService: AppSettingsService): Any { // GlideUrl Or String
|
||||
if (this.isEmptyOrNullOrNullString()) {
|
||||
return ""
|
||||
}
|
||||
if (appSettingsService.getBasicUserName().isNotEmpty()) {
|
||||
val authString = "${appSettingsService.getBasicUserName()}:${appSettingsService.getBasicPassword()}"
|
||||
val authBuf = Base64.encode(authString.toByteArray(Charsets.UTF_8))
|
||||
|
||||
return GlideUrl(
|
||||
this,
|
||||
LazyHeaders
|
||||
.Builder()
|
||||
.addHeader("Authorization", "Basic $authBuf")
|
||||
.build(),
|
||||
)
|
||||
} else {
|
||||
return GlideUrl(
|
||||
this,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun WebView.getGlideImageForResource(
|
||||
url: String,
|
||||
appSettingsService: AppSettingsService,
|
||||
) = Glide
|
||||
.with(this)
|
||||
.asBitmap()
|
||||
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL))
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
.submit()
|
||||
.get()
|
||||
|
||||
fun Context.preloadImage(
|
||||
url: String,
|
||||
appSettingsService: AppSettingsService,
|
||||
) = Glide
|
||||
.with(this)
|
||||
.asBitmap()
|
||||
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(PRELOAD_IMAGE_TIMEOUT))
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
.submit()
|
||||
|
||||
fun Context.imageIntoViewTarget(
|
||||
url: String,
|
||||
target: ViewTarget<Chip?, Drawable?>,
|
||||
appSettingsService: AppSettingsService,
|
||||
) = Glide
|
||||
.with(this)
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
.into(target)
|
||||
|
||||
fun Context.bitmapWithCache(
|
||||
url: String,
|
||||
iv: ImageView,
|
||||
appSettingsService: AppSettingsService,
|
||||
) = Glide
|
||||
.with(this)
|
||||
.asBitmap()
|
||||
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL))
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
.into(iv)
|
||||
|
||||
fun Context.bitmapCenterCrop(
|
||||
url: String,
|
||||
iv: ImageView,
|
||||
appSettingsService: AppSettingsService,
|
||||
) = Glide
|
||||
.with(this)
|
||||
) = Glide.with(this)
|
||||
.asBitmap()
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
.load(url)
|
||||
.apply(RequestOptions.centerCropTransform())
|
||||
.into(iv)
|
||||
|
||||
fun Context.bitmapFitCenter(
|
||||
url: String,
|
||||
iv: ImageView,
|
||||
appSettingsService: AppSettingsService,
|
||||
) = Glide
|
||||
.with(this)
|
||||
.asBitmap()
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(iv)
|
||||
|
||||
fun Context.circularDrawable(
|
||||
url: String,
|
||||
view: CircleImageView,
|
||||
appSettingsService: AppSettingsService,
|
||||
) {
|
||||
view.textView.text = ""
|
||||
|
||||
Glide
|
||||
.with(this)
|
||||
.load(url.toGlideUrl(appSettingsService))
|
||||
Glide.with(this)
|
||||
.load(url)
|
||||
.into(view.imageView)
|
||||
}
|
||||
|
||||
private const val BITMAP_INPUT_STREAM_COMPRESSION_QUALITY = 80
|
||||
|
||||
fun getBitmapInputStream(
|
||||
bitmap: Bitmap,
|
||||
compressFormat: Bitmap.CompressFormat,
|
||||
): InputStream {
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
bitmap.compress(compressFormat, BITMAP_INPUT_STREAM_COMPRESSION_QUALITY, byteArrayOutputStream)
|
||||
bitmap.compress(compressFormat, 80, byteArrayOutputStream)
|
||||
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
|
||||
return ByteArrayInputStream(bitmapData)
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.android.utils.network
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
lateinit var s: Snackbar
|
||||
@@ -10,13 +11,19 @@ lateinit var s: Snackbar
|
||||
fun isNetworkAccessible(context: Context): Boolean {
|
||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) ?: return false
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val network = connectivityManager.activeNetwork ?: return false
|
||||
val networkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
|
||||
|
||||
return when {
|
||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
|
||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
||||
else -> false
|
||||
return when {
|
||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
|
||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
||||
else -> false
|
||||
}
|
||||
} else {
|
||||
val network = connectivityManager.activeNetworkInfo ?: return false
|
||||
return network.isConnectedOrConnecting
|
||||
}
|
||||
}
|
||||
|
@@ -7,9 +7,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AppViewModel(
|
||||
private val repository: Repository,
|
||||
) : ViewModel() {
|
||||
class AppViewModel(private val repository: Repository) : ViewModel() {
|
||||
private val _networkAvailableProvider = MutableSharedFlow<Boolean>()
|
||||
val networkAvailableProvider = _networkAvailableProvider.asSharedFlow()
|
||||
private var wasConnected = true
|
||||
|
@@ -71,13 +71,35 @@
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
<com.leinardi.android.speeddial.SpeedDialView
|
||||
android:id="@+id/speedDial"
|
||||
android:layout_width="wrap_content"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
app:layout_behavior="@string/speeddial_scrolling_view_snackbar_behavior"
|
||||
app:sdMainFabClosedSrc="@drawable/ic_add_white_24dp" />
|
||||
android:layout_gravity="start|bottom|end"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<com.github.rubensousa.floatingtoolbar.FloatingToolbar
|
||||
android:id="@+id/floatingToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:layout_gravity="bottom"
|
||||
app:floatingMenu="@menu/reader_toolbar" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:src="@drawable/ic_add_white_24dp"
|
||||
app:backgroundTint="?attr/colorAccent"
|
||||
app:fabSize="mini"
|
||||
app:rippleColor="?attr/colorAccentDark" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/progressBar"
|
||||
@@ -97,5 +119,4 @@
|
||||
android:progressTint="?attr/colorAccent" />
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
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_justify">Justify</string>
|
||||
<string name="settings_reader_font">Reader font</string>
|
||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||
<string name="remove_source">Remove source</string>
|
||||
<string name="pref_theme_title">Light/Dark mode</string>
|
||||
<string name="mode_dark">Dark mode</string>
|
||||
|
@@ -106,6 +106,9 @@
|
||||
<string name="reader_text_align_left">Linksbündig</string>
|
||||
<string name="reader_text_align_justify">Blocksatz</string>
|
||||
<string name="settings_reader_font">Schriftgröße im Lesemodus</string>
|
||||
<string name="reader_static_bar_title">Statische untere Leiste im Lesemodus</string>
|
||||
<string name="reader_static_bar_on">Die untere Leiste wird dauerhaft angezeigt</string>
|
||||
<string name="reader_static_bar_off">Die untere Leiste kann über einen schwebenden Button angezeigt werden</string>
|
||||
<string name="remove_source">Quelle entfernen</string>
|
||||
<string name="pref_theme_title">Heller/Dunkler Modus</string>
|
||||
<string name="mode_dark">Dunkler Modus</string>
|
||||
|
@@ -106,6 +106,9 @@
|
||||
<string name="reader_text_align_left">Alinear a la izquierda</string>
|
||||
<string name="reader_text_align_justify">Justificado</string>
|
||||
<string name="settings_reader_font">Modo lectura</string>
|
||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||
<string name="remove_source">Remove source</string>
|
||||
<string name="pref_theme_title">Light/Dark mode</string>
|
||||
<string name="mode_dark">Dark mode</string>
|
||||
|
@@ -106,6 +106,9 @@
|
||||
<string name="reader_text_align_left">Aligner à gauche</string>
|
||||
<string name="reader_text_align_justify">Justifier le texte</string>
|
||||
<string name="settings_reader_font">Police du lecteur d\'articles</string>
|
||||
<string name="reader_static_bar_title">Barre statique pour le visionneur d\'articles</string>
|
||||
<string name="reader_static_bar_on">La barre sera affichée</string>
|
||||
<string name="reader_static_bar_off">La barre sera affichée grâce au bouton</string>
|
||||
<string name="remove_source">Supprimer la source</string>
|
||||
<string name="pref_theme_title">Thème Clair/Sombre</string>
|
||||
<string name="mode_dark">Thème sombre</string>
|
||||
|
@@ -106,6 +106,9 @@
|
||||
<string name="reader_text_align_left">Aliñar á esquerda</string>
|
||||
<string name="reader_text_align_justify">Xustificado</string>
|
||||
<string name="settings_reader_font">Modo lector</string>
|
||||
<string name="reader_static_bar_title">Barra inferior estática na vista de artigos</string>
|
||||
<string name="reader_static_bar_on">A barra inferior mostrarase sempre</string>
|
||||
<string name="reader_static_bar_off">A barra inferior pode mostrarse a través do botón flotante</string>
|
||||
<string name="remove_source">Eliminar fonte</string>
|
||||
<string name="pref_theme_title">Modo Claro/Escuro</string>
|
||||
<string name="mode_dark">Modo escuro</string>
|
||||
|
@@ -106,6 +106,9 @@
|
||||
<string name="reader_text_align_left">Align left</string>
|
||||
<string name="reader_text_align_justify">Justify</string>
|
||||
<string name="settings_reader_font">Reader font</string>
|
||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||
<string name="remove_source">Remove source</string>
|
||||
<string name="pref_theme_title">Light/Dark mode</string>
|
||||
<string name="mode_dark">Dark mode</string>
|
||||
|
@@ -106,6 +106,9 @@
|
||||
<string name="reader_text_align_left">Align left</string>
|
||||
<string name="reader_text_align_justify">Justify</string>
|
||||
<string name="settings_reader_font">Reader font</string>
|
||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||
<string name="remove_source">Remove source</string>
|
||||
<string name="pref_theme_title">Light/Dark mode</string>
|
||||
<string name="mode_dark">Dark mode</string>
|
||||
|
@@ -106,6 +106,9 @@
|
||||
<string name="reader_text_align_left">Align left</string>
|
||||
<string name="reader_text_align_justify">Justify</string>
|
||||
<string name="settings_reader_font">Reader font</string>
|
||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||
<string name="remove_source">Remove source</string>
|
||||
<string name="pref_theme_title">Light/Dark mode</string>
|
||||
<string name="mode_dark">Dark mode</string>
|
||||
|
@@ -23,7 +23,6 @@
|
||||
<string name="wrong_infos">"Controleer de gegevens nogmaals."</string>
|
||||
<string name="all_posts_not_read">"Fout bij markeren als gelezen"</string>
|
||||
<string name="all_posts_read">"Alle artikelen gemarkeerd als gelezen"</string>
|
||||
<string name="undo_string">"Ongedaan maken"</string>
|
||||
<string name="addStringNoUrl">"Login om bronnen toe te voegen"</string>
|
||||
<string name="cant_get_sources">"Kan de lijst met bronnen niet ophalen"</string>
|
||||
<string name="cant_create_source">"Kan bron niet creëeren"</string>
|
||||
@@ -106,6 +105,9 @@
|
||||
<string name="reader_text_align_left">Align left</string>
|
||||
<string name="reader_text_align_justify">Justify</string>
|
||||
<string name="settings_reader_font">Reader font</string>
|
||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||
<string name="remove_source">Remove source</string>
|
||||
<string name="pref_theme_title">Light/Dark mode</string>
|
||||
<string name="mode_dark">Dark mode</string>
|
||||
@@ -129,4 +131,5 @@
|
||||
<string name="action_about">"Over"</string>
|
||||
<string name="marked_as_read">"Artikel gelezen"</string>
|
||||
<string name="marked_as_unread">"Item unread"</string>
|
||||
<string name="undo_string">"Ongedaan maken"</string>
|
||||
</resources>
|
@@ -106,6 +106,9 @@
|
||||
<string name="reader_text_align_left">Align left</string>
|
||||
<string name="reader_text_align_justify">Justify</string>
|
||||
<string name="settings_reader_font">Reader font</string>
|
||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||
<string name="remove_source">Remove source</string>
|
||||
<string name="pref_theme_title">Light/Dark mode</string>
|
||||
<string name="mode_dark">Dark mode</string>
|
||||
|
@@ -106,6 +106,9 @@
|
||||
<string name="reader_text_align_left">Align left</string>
|
||||
<string name="reader_text_align_justify">Justify</string>
|
||||
<string name="settings_reader_font">Reader font</string>
|
||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||
<string name="remove_source">Remove source</string>
|
||||
<string name="pref_theme_title">Light/Dark mode</string>
|
||||
<string name="mode_dark">Dark mode</string>
|
||||
|
@@ -106,6 +106,9 @@
|
||||
<string name="reader_text_align_left">Align left</string>
|
||||
<string name="reader_text_align_justify">Justify</string>
|
||||
<string name="settings_reader_font">Reader font</string>
|
||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||
<string name="remove_source">Remove source</string>
|
||||
<string name="pref_theme_title">Light/Dark mode</string>
|
||||
<string name="mode_dark">Dark mode</string>
|
||||
|
@@ -106,6 +106,9 @@
|
||||
<string name="reader_text_align_left">Align left</string>
|
||||
<string name="reader_text_align_justify">Justify</string>
|
||||
<string name="settings_reader_font">Reader font</string>
|
||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||
<string name="remove_source">Remove source</string>
|
||||
<string name="pref_theme_title">Light/Dark mode</string>
|
||||
<string name="mode_dark">Dark mode</string>
|
||||
|
@@ -106,6 +106,9 @@
|
||||
<string name="reader_text_align_left">左对齐</string>
|
||||
<string name="reader_text_align_justify">左右对齐</string>
|
||||
<string name="settings_reader_font">阅读器字体</string>
|
||||
<string name="reader_static_bar_title">文章查看器中的静态底部栏</string>
|
||||
<string name="reader_static_bar_on">底部栏将始终显示</string>
|
||||
<string name="reader_static_bar_off">底部栏可以通过浮动按钮显示</string>
|
||||
<string name="remove_source">删除源</string>
|
||||
<string name="pref_theme_title">浅色/深色模式</string>
|
||||
<string name="mode_dark">深色模式</string>
|
||||
|
@@ -106,6 +106,9 @@
|
||||
<string name="reader_text_align_left">Align left</string>
|
||||
<string name="reader_text_align_justify">Justify</string>
|
||||
<string name="settings_reader_font">Reader font</string>
|
||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||
<string name="remove_source">Remove source</string>
|
||||
<string name="pref_theme_title">Light/Dark mode</string>
|
||||
<string name="mode_dark">Dark mode</string>
|
||||
|
@@ -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="open_sans_font_id" translatable="false">open_sans</string>
|
||||
<string name="roboto_font_id" translatable="false">roboto</string>
|
||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||
<string name="remove_source">Remove source</string>
|
||||
<string name="pref_theme_title">Light/Dark mode</string>
|
||||
<string name="mode_dark">Dark mode</string>
|
||||
|
@@ -30,6 +30,14 @@
|
||||
android:summaryOn="@string/pref_article_viewer_on"
|
||||
android:title="@string/pref_article_viewer_title"
|
||||
app:iconSpaceReserved="false"/>
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:dependency="prefer_article_viewer"
|
||||
android:key="reader_static_bar"
|
||||
android:summaryOff="@string/reader_static_bar_off"
|
||||
android:summaryOn="@string/reader_static_bar_on"
|
||||
android:title="@string/reader_static_bar_title"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_general_category_displaying">
|
||||
|
@@ -1,5 +1,3 @@
|
||||
@file:Suppress("detekt:LargeClass")
|
||||
|
||||
package bou.amine.apps.readerforselfossv2.tests.repository
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
||||
@@ -52,6 +50,7 @@ class RepositoryTest {
|
||||
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
||||
private val appSettingsService = mockk<AppSettingsService>()
|
||||
private val api = mockk<SelfossApi>()
|
||||
|
||||
private lateinit var repository: Repository
|
||||
|
||||
private fun initializeRepository(
|
||||
|
@@ -44,7 +44,7 @@ class FakeItemParameters {
|
||||
var datetime = "2022-09-09T03:32:01-04:00"
|
||||
val title = "Etica della ricerca sotto i riflettori."
|
||||
val content =
|
||||
"<p><strong>Luigi Campanella, già Presidente SCI</strong></p>\n<p>L’etica della scienza è di certo ambito di cui continuiamo a scoprire nuovi aspetti e risvolti.</p>\n<p>L’ultimo è quello delle intelligenze artificiali capaci di creare opere complesse basate su immagini e parole memorizzate con il rischio di fake news e di contenuti disturbanti.</p>\n<p>Per evitare che ciò accada si sta procedendo filtrando secondo criteri di autocensura i dati da cui l’intelligenza artificiale parte.</p>\n<p>Comincia ad intravedersi un futuro prossimo di competizione fra autori umani ed artificiali nel quale sarà importante, quando i loro prodotti saranno indistinguibili, dichiararne l’origine.</p>\n<p>Come si comprende, si conferma che gli aspetti etici dell’innovazione e della ricerca si diversificato sempre di più.</p>\n<p>La biologia molecolare e la genetica già in passato hanno posto all’attenzione comune aspetti di etica della scienza che hanno indotto a nuove riflessioni circa i limiti delle ricerche.</p>\n<p>L’argomento, sempre attuale, torna sulle prime pagine a seguito della pubblicazione di una ricerca della Università di Cambridge che ha sviluppato una struttura cellulare di un topo con un cuore che batte regolarmente.</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image002-1.png?w=481\" alt=\"\" width=\"697\" height=\"430\" /><img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image003-1.png?w=906\" alt=\"\" /><p>Magdalena Zernicka-Goetz</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image004.jpg?w=474\" alt=\"\" width=\"622\" height=\"465\" /><p>Gianluca Amadei</p>\n<p>Del gruppo fa parte anche uno scienziato italiano Gianluca Amadei,che dinnanzi alle obiezioni di natura etica sulla realizzazione della vita artificiale si è affrettato a sostenere che non è creare nuove vite il fine primario della ricerca, ma quello di salvare quelle esistenti, di dare contributi essenziali alla medicina citando il caso del fallimento tuttora non interpretato di alcune gravidanze e di superare la sperimentazione animale, così contribuendo positivamente alla soluzione di un altro dilemma etico.</p>\n<p>L’embrione sintetico ha ovviamente come primo traguardo il contributo ai trapianti oggi drammaticamente carenti nell’offerta rispetto alla domanda, con attese fino a 4 anni per i trapianti di cuore ed a 2 anni per quelli di fegato. Il lavoro dovrebbe adesso continuare presso l’Ateneo di Padova per creare nuovi organi e nuovi farmaci.</p>"
|
||||
"<p><strong>Luigi Campanella, già Presidente SCI</strong></p>\n<p>L’etica della scienza è di certo ambito di cui continuiamo</p>"
|
||||
var unread = true
|
||||
var starred = true
|
||||
val thumbnail = null
|
||||
|
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.useAndroidX=true
|
||||
#android.nonTransitiveRClass=true
|
||||
android.enableJetifier=false
|
||||
android.enableJetifier=true
|
||||
android.nonTransitiveRClass=false
|
||||
#MPP
|
||||
kotlin.mpp.enableCInteropCommonization=true
|
||||
|
@@ -4,13 +4,12 @@ import android.content.Context
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
||||
|
||||
actual class DriverFactory(
|
||||
private val context: Context,
|
||||
) {
|
||||
actual fun createDriver(): SqlDriver =
|
||||
AndroidSqliteDriver(
|
||||
actual class DriverFactory(private val context: Context) {
|
||||
actual fun createDriver(): SqlDriver {
|
||||
return AndroidSqliteDriver(
|
||||
ReaderForSelfossDB.Schema,
|
||||
context,
|
||||
"ReaderForSelfossV2-android.db",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -9,14 +9,12 @@ class NaiveTrustManager : X509TrustManager {
|
||||
chain: Array<out X509Certificate>?,
|
||||
authType: String?,
|
||||
) {
|
||||
// Nothing
|
||||
}
|
||||
|
||||
override fun checkServerTrusted(
|
||||
chain: Array<out X509Certificate>?,
|
||||
authType: String?,
|
||||
) {
|
||||
// Nothing
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
|
||||
|
@@ -3,7 +3,6 @@ package bou.amine.apps.readerforselfossv2.utils
|
||||
import android.text.format.DateUtils
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
||||
actual class DateUtils {
|
||||
actual companion object {
|
||||
actual fun parseRelativeDate(dateString: String): String {
|
||||
|
@@ -12,14 +12,16 @@ actual fun SelfossModel.Item.getIcon(baseUrl: String): String = constructUrl(bas
|
||||
|
||||
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String = constructUrl(baseUrl, "thumbnails", thumbnail)
|
||||
|
||||
val IMAGE_EXTENSION_REGEXP = """\.(jpg|jpeg|png|webp)""".toRegex()
|
||||
|
||||
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
||||
val allImages = ArrayList<String>()
|
||||
|
||||
for (image in Jsoup.parse(content).getElementsByTag("img")) {
|
||||
val url = image.attr("src")
|
||||
if (IMAGE_EXTENSION_REGEXP.containsMatchIn(url.lowercase(Locale.US))) {
|
||||
if (url.lowercase(Locale.US).contains(".jpg") ||
|
||||
url.lowercase(Locale.US).contains(".jpeg") ||
|
||||
url.lowercase(Locale.US).contains(".png") ||
|
||||
url.lowercase(Locale.US).contains(".webp")
|
||||
) {
|
||||
allImages.add(url)
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,8 @@
|
||||
@file:Suppress("detekt:LongParameterList")
|
||||
|
||||
package bou.amine.apps.readerforselfossv2.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
class MercuryModel {
|
||||
@Suppress("detekt:ConstructorParameterNaming")
|
||||
@Serializable
|
||||
class ParsedContent(
|
||||
val title: String? = null,
|
||||
|
@@ -1,5 +1,3 @@
|
||||
@file:Suppress("detekt:LongParameterList")
|
||||
|
||||
package bou.amine.apps.readerforselfossv2.model
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||
@@ -20,10 +18,6 @@ import kotlinx.serialization.json.booleanOrNull
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
class ModelException(
|
||||
message: String,
|
||||
) : Throwable(message)
|
||||
|
||||
class SelfossModel {
|
||||
@Serializable
|
||||
data class Tag(
|
||||
@@ -147,7 +141,7 @@ class SelfossModel {
|
||||
}
|
||||
|
||||
if (stringUrl.isEmptyOrNullOrNullString()) {
|
||||
throw ModelException("Link $link was translated to $stringUrl, but was empty. Handle this.")
|
||||
throw Exception("Link $link was translated to $stringUrl, but was empty. Handle this.")
|
||||
}
|
||||
|
||||
return stringUrl
|
||||
@@ -176,7 +170,7 @@ class SelfossModel {
|
||||
}
|
||||
}
|
||||
|
||||
// this seems to be super slow.
|
||||
// TODO: this seems to be super slow.
|
||||
object TagsListSerializer : KSerializer<List<String>> {
|
||||
override fun deserialize(decoder: Decoder): List<String> =
|
||||
when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {
|
||||
|
@@ -1,5 +1,3 @@
|
||||
@file:Suppress("detekt:TooManyFunctions")
|
||||
|
||||
package bou.amine.apps.readerforselfossv2.repository
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.dao.ACTION
|
||||
@@ -13,7 +11,7 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||
import bou.amine.apps.readerforselfossv2.utils.Enums.ItemType
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.toEntity
|
||||
import bou.amine.apps.readerforselfossv2.utils.toParsedDate
|
||||
@@ -25,8 +23,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val MAX_ITEMS_NUMBER = 200
|
||||
|
||||
class Repository(
|
||||
private val api: SelfossApi,
|
||||
private val appSettingsService: AppSettingsService,
|
||||
@@ -40,26 +36,26 @@ class Repository(
|
||||
|
||||
var displayedItems = ItemType.UNREAD
|
||||
|
||||
private var _tagFilter = MutableStateFlow<SelfossModel.Tag?>(null)
|
||||
var tagFilter = _tagFilter.asStateFlow()
|
||||
private var _sourceFilter = MutableStateFlow<SelfossModel.Source?>(null)
|
||||
var sourceFilter = _sourceFilter.asStateFlow()
|
||||
private var tagFilterFlow = MutableStateFlow<SelfossModel.Tag?>(null)
|
||||
var tagFilter = tagFilterFlow.asStateFlow()
|
||||
private var sourceFilterFlow = MutableStateFlow<SelfossModel.Source?>(null)
|
||||
var sourceFilter = sourceFilterFlow.asStateFlow()
|
||||
var searchFilter: String? = null
|
||||
|
||||
var offlineOverride = false
|
||||
|
||||
private val _badgeUnread = MutableStateFlow(0)
|
||||
val badgeUnread = _badgeUnread.asStateFlow()
|
||||
private val _badgeAll = MutableStateFlow(0)
|
||||
val badgeAll = _badgeAll.asStateFlow()
|
||||
private val _badgeStarred = MutableStateFlow(0)
|
||||
val badgeStarred = _badgeStarred.asStateFlow()
|
||||
private val badgeUnreadFlow = MutableStateFlow(0)
|
||||
val badgeUnread = badgeUnreadFlow.asStateFlow()
|
||||
private val badgeAllFlow = MutableStateFlow(0)
|
||||
val badgeAll = badgeAllFlow.asStateFlow()
|
||||
private val badgeStarredFlow = MutableStateFlow(0)
|
||||
val badgeStarred = badgeStarredFlow.asStateFlow()
|
||||
|
||||
private var fetchedTags = false
|
||||
private var fetchedSources = false
|
||||
|
||||
private var _readerItems = ArrayList<SelfossModel.Item>()
|
||||
private var _selectedSource: SelfossModel.SourceDetail? = null
|
||||
private var readerItems = ArrayList<SelfossModel.Item>()
|
||||
private var selectedSource: SelfossModel.SourceDetail? = null
|
||||
|
||||
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
||||
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
||||
@@ -131,7 +127,7 @@ class Repository(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
MAX_ITEMS_NUMBER,
|
||||
200,
|
||||
)
|
||||
return if (items.success && items.data != null) {
|
||||
items.data
|
||||
@@ -143,23 +139,22 @@ class Repository(
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("detekt:ForbiddenComment")
|
||||
suspend fun reloadBadges(): Boolean {
|
||||
var success = false
|
||||
if (isNetworkAvailable()) {
|
||||
val response = api.stats()
|
||||
if (response.success && response.data != null) {
|
||||
_badgeUnread.value = response.data.unread ?: 0
|
||||
_badgeAll.value = response.data.total
|
||||
_badgeStarred.value = response.data.starred ?: 0
|
||||
badgeUnreadFlow.value = response.data.unread ?: 0
|
||||
badgeAllFlow.value = response.data.total
|
||||
badgeStarredFlow.value = response.data.starred ?: 0
|
||||
success = true
|
||||
}
|
||||
} else if (appSettingsService.isItemCachingEnabled()) {
|
||||
// TODO: do this differently, because it's not efficient
|
||||
val dbItems = getDBItems()
|
||||
_badgeUnread.value = dbItems.filter { item -> item.unread }.size
|
||||
_badgeStarred.value = dbItems.filter { item -> item.starred }.size
|
||||
_badgeAll.value = dbItems.size
|
||||
badgeUnreadFlow.value = dbItems.filter { item -> item.unread }.size
|
||||
badgeStarredFlow.value = dbItems.filter { item -> item.starred }.size
|
||||
badgeAllFlow.value = dbItems.size
|
||||
success = true
|
||||
}
|
||||
return success
|
||||
@@ -321,7 +316,7 @@ class Repository(
|
||||
private fun markAsReadLocally(item: SelfossModel.Item) {
|
||||
if (item.unread) {
|
||||
item.unread = false
|
||||
_badgeUnread.value -= 1
|
||||
badgeUnreadFlow.value -= 1
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
@@ -332,7 +327,7 @@ class Repository(
|
||||
private fun unmarkAsReadLocally(item: SelfossModel.Item) {
|
||||
if (!item.unread) {
|
||||
item.unread = true
|
||||
_badgeUnread.value += 1
|
||||
badgeUnreadFlow.value += 1
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
@@ -343,7 +338,7 @@ class Repository(
|
||||
private fun starrLocally(item: SelfossModel.Item) {
|
||||
if (!item.starred) {
|
||||
item.starred = true
|
||||
_badgeStarred.value += 1
|
||||
badgeStarredFlow.value += 1
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
@@ -354,7 +349,7 @@ class Repository(
|
||||
private fun unstarrLocally(item: SelfossModel.Item) {
|
||||
if (item.starred) {
|
||||
item.starred = false
|
||||
_badgeStarred.value -= 1
|
||||
badgeStarredFlow.value -= 1
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
@@ -564,7 +559,6 @@ class Repository(
|
||||
item.id.toString(),
|
||||
)
|
||||
|
||||
@Suppress("detekt:SwallowedException")
|
||||
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
|
||||
try {
|
||||
val newItems = getMaxItemsForBackground(ItemType.UNREAD)
|
||||
@@ -620,30 +614,30 @@ class Repository(
|
||||
}
|
||||
|
||||
fun setTagFilter(tag: SelfossModel.Tag?) {
|
||||
_tagFilter.value = tag
|
||||
tagFilterFlow.value = tag
|
||||
}
|
||||
|
||||
fun setSourceFilter(source: SelfossModel.Source?) {
|
||||
_sourceFilter.value = source
|
||||
sourceFilterFlow.value = source
|
||||
}
|
||||
|
||||
fun setReaderItems(readerItems: ArrayList<SelfossModel.Item>) {
|
||||
_readerItems = readerItems
|
||||
this.readerItems = readerItems
|
||||
}
|
||||
|
||||
fun getReaderItems(): ArrayList<SelfossModel.Item> = _readerItems
|
||||
fun getReaderItems(): ArrayList<SelfossModel.Item> = readerItems
|
||||
|
||||
fun migrate(driverFactory: DriverFactory) {
|
||||
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
||||
}
|
||||
|
||||
fun setSelectedSource(source: SelfossModel.SourceDetail) {
|
||||
_selectedSource = source
|
||||
selectedSource = source
|
||||
}
|
||||
|
||||
fun unsetSelectedSource() {
|
||||
_selectedSource = null
|
||||
selectedSource = null
|
||||
}
|
||||
|
||||
fun getSelectedSource(): SelfossModel.SourceDetail? = _selectedSource
|
||||
fun getSelectedSource(): SelfossModel.SourceDetail? = selectedSource
|
||||
}
|
||||
|
@@ -17,8 +17,8 @@ import kotlinx.serialization.json.Json
|
||||
class MercuryApi {
|
||||
var client = createHttpClient()
|
||||
|
||||
private fun createHttpClient(): HttpClient =
|
||||
HttpClient {
|
||||
private fun createHttpClient(): HttpClient {
|
||||
return HttpClient {
|
||||
install(HttpCache)
|
||||
install(ContentNegotiation) {
|
||||
json(
|
||||
@@ -40,6 +40,7 @@ class MercuryApi {
|
||||
}
|
||||
expectSuccess = false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> =
|
||||
bodyOrFailure(
|
||||
|
@@ -33,7 +33,6 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse =
|
||||
SuccessResponse(false)
|
||||
}
|
||||
|
||||
@Suppress("detekt:SwallowedException")
|
||||
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
|
||||
try {
|
||||
return if (r != null && r.status.isSuccess()) {
|
||||
|
@@ -1,5 +1,3 @@
|
||||
@file:Suppress("detekt:TooManyFunctions", "detekt:LongParameterList", "detekt:LargeClass")
|
||||
|
||||
package bou.amine.apps.readerforselfossv2.rest
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
@@ -37,8 +35,6 @@ import kotlinx.serialization.json.Json
|
||||
|
||||
expect fun setupInsecureHttpEngine(config: CIOEngineConfig)
|
||||
|
||||
private const val VERSION_WHERE_POST_LOGIN_SHOULD_WORK = 5
|
||||
|
||||
class SelfossApi(
|
||||
private val appSettingsService: AppSettingsService,
|
||||
) {
|
||||
@@ -180,7 +176,7 @@ class SelfossApi(
|
||||
},
|
||||
)
|
||||
|
||||
private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= VERSION_WHERE_POST_LOGIN_SHOULD_WORK // We are missing 4.1.0
|
||||
private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
|
||||
|
||||
suspend fun logout(): SuccessResponse =
|
||||
if (shouldHaveNewLogout()) {
|
||||
|
@@ -1,5 +1,3 @@
|
||||
@file:Suppress("detekt:TooManyFunctions")
|
||||
|
||||
package bou.amine.apps.readerforselfossv2.service
|
||||
|
||||
import com.russhwolf.settings.Settings
|
||||
|
@@ -1,19 +1,7 @@
|
||||
@file:Suppress("detekt:TooManyFunctions")
|
||||
|
||||
package bou.amine.apps.readerforselfossv2.service
|
||||
|
||||
import com.russhwolf.settings.Settings
|
||||
|
||||
private const val DEFAULT_FONT_SIZE = 16
|
||||
|
||||
private const val DEFAULT_REFRESH_MINUTES = 360L
|
||||
|
||||
private const val MIN_REFRESH_MINUTES = 15L
|
||||
|
||||
private const val DEFAULT_API_TIMEOUT = 60L
|
||||
|
||||
private const val DEFAULT_ITEMS_NUMBER = 20
|
||||
|
||||
class AppSettingsService(
|
||||
acraSenderServiceProcess: Boolean = false,
|
||||
) {
|
||||
@@ -48,11 +36,12 @@ class AppSettingsService(
|
||||
private var notifyNewItems: Boolean? = null
|
||||
private var itemsNumber: Int? = null
|
||||
private var apiTimeout: Long? = null
|
||||
private var refreshMinutes: Long = DEFAULT_REFRESH_MINUTES
|
||||
private var refreshMinutes: Long = 360
|
||||
private var markOnScroll: Boolean? = null
|
||||
private var activeAlignment: Int? = null
|
||||
|
||||
private var fontSize: Int? = null
|
||||
private var staticBar: Boolean? = null
|
||||
private var font: String = ""
|
||||
private var theme: Int? = null
|
||||
|
||||
@@ -152,14 +141,13 @@ class AppSettingsService(
|
||||
return itemsNumber!!
|
||||
}
|
||||
|
||||
@Suppress("detekt:SwallowedException")
|
||||
private fun refreshItemsNumber() {
|
||||
itemsNumber =
|
||||
try {
|
||||
settings.getString(API_ITEMS_NUMBER, DEFAULT_ITEMS_NUMBER.toString()).toInt()
|
||||
settings.getString(API_ITEMS_NUMBER, "20").toInt()
|
||||
} catch (e: Exception) {
|
||||
settings.remove(API_ITEMS_NUMBER)
|
||||
DEFAULT_ITEMS_NUMBER
|
||||
20
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,24 +158,22 @@ class AppSettingsService(
|
||||
return apiTimeout!!
|
||||
}
|
||||
|
||||
@Suppress("detekt:MagicNumber")
|
||||
private fun secToMs(n: Long) = n * 1000
|
||||
|
||||
@Suppress("detekt:SwallowedException")
|
||||
private fun refreshApiTimeout() {
|
||||
apiTimeout =
|
||||
secToMs(
|
||||
try {
|
||||
val settingsTimeout = settings.getString(API_TIMEOUT, DEFAULT_API_TIMEOUT.toString())
|
||||
val settingsTimeout = settings.getString(API_TIMEOUT, "60")
|
||||
if (settingsTimeout.toLong() > 0) {
|
||||
settingsTimeout.toLong()
|
||||
} else {
|
||||
settings.remove(API_TIMEOUT)
|
||||
DEFAULT_API_TIMEOUT
|
||||
60
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
settings.remove(API_TIMEOUT)
|
||||
DEFAULT_API_TIMEOUT
|
||||
60
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -301,14 +287,14 @@ class AppSettingsService(
|
||||
}
|
||||
|
||||
private fun refreshRefreshMinutes() {
|
||||
refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, DEFAULT_REFRESH_MINUTES.toString()).toLong()
|
||||
if (refreshMinutes <= MIN_REFRESH_MINUTES) {
|
||||
refreshMinutes = MIN_REFRESH_MINUTES
|
||||
refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, "360").toLong()
|
||||
if (refreshMinutes <= 15) {
|
||||
refreshMinutes = 15
|
||||
}
|
||||
}
|
||||
|
||||
fun getRefreshMinutes(): Long {
|
||||
if (refreshMinutes != DEFAULT_REFRESH_MINUTES) {
|
||||
if (refreshMinutes != 360L) {
|
||||
refreshRefreshMinutes()
|
||||
}
|
||||
return refreshMinutes
|
||||
@@ -382,7 +368,18 @@ class AppSettingsService(
|
||||
if (fontSize != null) {
|
||||
refreshFontSize()
|
||||
}
|
||||
return fontSize ?: DEFAULT_FONT_SIZE
|
||||
return fontSize ?: 16
|
||||
}
|
||||
|
||||
private fun refreshStaticBarEnabled() {
|
||||
staticBar = settings.getBoolean(READER_STATIC_BAR, false)
|
||||
}
|
||||
|
||||
fun isStaticBarEnabled(): Boolean {
|
||||
if (staticBar != null) {
|
||||
refreshStaticBarEnabled()
|
||||
}
|
||||
return staticBar == true
|
||||
}
|
||||
|
||||
private fun refreshFont() {
|
||||
@@ -437,6 +434,7 @@ class AppSettingsService(
|
||||
refreshActiveAllignment()
|
||||
refreshFontSize()
|
||||
refreshFont()
|
||||
refreshStaticBarEnabled()
|
||||
refreshCurrentTheme()
|
||||
}
|
||||
|
||||
@@ -486,11 +484,11 @@ class AppSettingsService(
|
||||
|
||||
const val SOURCE_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform"
|
||||
|
||||
const val BUG_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
||||
const val TRACKER_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
||||
|
||||
const val SYNC_CHANNEL_ID = "sync-channel-id"
|
||||
|
||||
const val NEW_ITEMS_CHANNEL = "new-items-channel-id"
|
||||
const val NEW_ITEMS_CHANNEL_ID = "new-items-channel-id"
|
||||
|
||||
const val JUSTIFY = 1
|
||||
|
||||
@@ -534,6 +532,8 @@ class AppSettingsService(
|
||||
|
||||
const val READER_FONT = "reader_font"
|
||||
|
||||
const val READER_STATIC_BAR = "reader_static_bar"
|
||||
|
||||
const val READER_FONT_SIZE = "reader_font_size"
|
||||
|
||||
const val TEXT_ALIGN = "text_align"
|
||||
|
@@ -4,12 +4,6 @@ import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toInstant
|
||||
|
||||
class DateParseException(
|
||||
message: String,
|
||||
e: Throwable? = null,
|
||||
) : Throwable(message, e)
|
||||
|
||||
@Suppress("detekt:ThrowsCount")
|
||||
fun String.toParsedDate(): Long {
|
||||
// Possible formats are
|
||||
// yyyy-mm-dd hh:mm:ss format
|
||||
@@ -23,22 +17,17 @@ fun String.toParsedDate(): Long {
|
||||
if (this.matches(oldVersionFormat)) {
|
||||
this.replace(" ", "T")
|
||||
} else if (this.matches(newVersionFormat)) {
|
||||
newVersionFormat
|
||||
.find(this)
|
||||
?.groups
|
||||
?.get(1)
|
||||
?.value ?: throw DateParseException("Couldn't parse $this")
|
||||
newVersionFormat.find(this)?.groups?.get(1)?.value ?: throw Exception("Couldn't parse $this")
|
||||
} else {
|
||||
throw DateParseException("Unrecognized format for $this")
|
||||
throw Exception("Unrecognized format for $this")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw DateParseException("parseDate failed for $this", e)
|
||||
throw Exception("parseDate failed for $this", e)
|
||||
}
|
||||
|
||||
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
|
||||
}
|
||||
|
||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
||||
expect class DateUtils() {
|
||||
companion object {
|
||||
fun parseRelativeDate(dateString: String): String
|
||||
|
@@ -17,7 +17,7 @@ fun SOURCE.toView(): SelfossModel.SourceDetail =
|
||||
this.id.toInt(),
|
||||
this.title,
|
||||
null,
|
||||
this.tags.split(","),
|
||||
this.tags?.split(","),
|
||||
this.spout,
|
||||
this.error,
|
||||
this.icon,
|
||||
@@ -74,7 +74,6 @@ fun SelfossModel.Item.toEntity(): ITEM =
|
||||
this.author,
|
||||
)
|
||||
|
||||
@Suppress("detekt:MagicNumber")
|
||||
fun SelfossModel.Tag.getColorHexCode(): String =
|
||||
if (this.color.length == 4) { // #000
|
||||
val char1 = this.color.get(1)
|
||||
|
@@ -0,0 +1,17 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
object Enums {
|
||||
enum class ItemType(
|
||||
val position: Int,
|
||||
val type: String,
|
||||
) {
|
||||
UNREAD(1, "unread"),
|
||||
ALL(2, "all"),
|
||||
STARRED(3, "starred"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int) = values().first { it.position == value }
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
@Suppress("detekt:MagicNumber")
|
||||
enum class ItemType(
|
||||
val position: Int,
|
||||
val type: String,
|
||||
) {
|
||||
UNREAD(1, "unread"),
|
||||
ALL(2, "all"),
|
||||
STARRED(3, "starred"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int) = values().first { it.position == value }
|
||||
}
|
||||
}
|
@@ -2,7 +2,6 @@ package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
fun String?.isEmptyOrNullOrNullString(): Boolean = this == null || this == "null" || this.isEmpty()
|
||||
|
||||
@Suppress("detekt:MagicNumber")
|
||||
fun String.longHash(): Long {
|
||||
var h = 98764321261L
|
||||
val l = this.length
|
||||
|
@@ -18,8 +18,7 @@ class DatesTest {
|
||||
fun new_version_date_should_be_parsed() {
|
||||
val date = newVersionDate.toParsedDate()
|
||||
val expected =
|
||||
LocalDateTime(2013, 4, 7, 13, 43, 0, 0)
|
||||
.toInstant(TimeZone.currentSystemDefault())
|
||||
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||
.toEpochMilliseconds()
|
||||
|
||||
assertEquals(expected, date)
|
||||
@@ -29,8 +28,7 @@ class DatesTest {
|
||||
fun new_version_date2_should_be_parsed() {
|
||||
val date = newVersionDate2.toParsedDate()
|
||||
val expected =
|
||||
LocalDateTime(2013, 4, 7, 13, 43, 0, 0)
|
||||
.toInstant(TimeZone.currentSystemDefault())
|
||||
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||
.toEpochMilliseconds()
|
||||
|
||||
assertEquals(expected, date)
|
||||
@@ -40,8 +38,7 @@ class DatesTest {
|
||||
fun old_version_date_should_be_parsed() {
|
||||
val date = oldVersionDate.toParsedDate()
|
||||
val expected =
|
||||
LocalDateTime(2013, 5, 7, 13, 46, 0, 0)
|
||||
.toInstant(TimeZone.currentSystemDefault())
|
||||
LocalDateTime(2013, 5, 7, 13, 46, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||
.toEpochMilliseconds()
|
||||
|
||||
assertEquals(expected, date)
|
||||
@@ -51,8 +48,7 @@ class DatesTest {
|
||||
fun old_version_variant_date_should_be_parsed() {
|
||||
val date = oldVersionDateVariant.toParsedDate()
|
||||
val expected =
|
||||
LocalDateTime(2021, 3, 21, 10, 32, 0, 0)
|
||||
.toInstant(TimeZone.currentSystemDefault())
|
||||
LocalDateTime(2021, 3, 21, 10, 32, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||
.toEpochMilliseconds()
|
||||
|
||||
assertEquals(expected, date)
|
||||
@@ -62,8 +58,7 @@ class DatesTest {
|
||||
fun new_version_variant_date_should_be_parsed() {
|
||||
val date = newVersionDateVariant.toParsedDate()
|
||||
val expected =
|
||||
LocalDateTime(2022, 12, 24, 17, 0, 8, 0)
|
||||
.toInstant(TimeZone.currentSystemDefault())
|
||||
LocalDateTime(2022, 12, 24, 17, 0, 8, 0).toInstant(TimeZone.currentSystemDefault())
|
||||
.toEpochMilliseconds()
|
||||
|
||||
assertEquals(expected, date)
|
@@ -4,5 +4,7 @@ import app.cash.sqldelight.db.SqlDriver
|
||||
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
||||
|
||||
actual class DriverFactory {
|
||||
actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||
actual fun createDriver(): SqlDriver {
|
||||
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,5 @@ package bou.amine.apps.readerforselfossv2.rest
|
||||
|
||||
import io.ktor.client.engine.cio.CIOEngineConfig
|
||||
|
||||
@Suppress("detekt:EmptyFunctionBlock")
|
||||
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
||||
actual class DateUtils {
|
||||
actual companion object {
|
||||
actual fun parseRelativeDate(dateString: String): String {
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
||||
actual class DateUtils actual constructor() {
|
||||
actual companion object {
|
||||
actual fun parseRelativeDate(dateString: String): String {
|
@@ -4,5 +4,7 @@ import app.cash.sqldelight.db.SqlDriver
|
||||
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
||||
|
||||
actual class DriverFactory {
|
||||
actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||
actual fun createDriver(): SqlDriver {
|
||||
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,5 @@ package bou.amine.apps.readerforselfossv2.rest
|
||||
|
||||
import io.ktor.client.engine.cio.CIOEngineConfig
|
||||
|
||||
@Suppress("detekt:EmptyFunctionBlock")
|
||||
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
||||
actual class DateUtils {
|
||||
actual companion object {
|
||||
actual fun parseRelativeDate(dateString: String): String {
|
||||
|
Reference in New Issue
Block a user