Debug trying to fix context issues. (#174)
Reviewed-on: #174 Co-authored-by: Amine <amine.bouabdallaoui@pm.me> Co-committed-by: Amine <amine.bouabdallaoui@pm.me>
This commit is contained in:
parent
54dbda76ab
commit
c79ab5e92b
36
.editorconfig
Normal file
36
.editorconfig
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[.editorconfig]
|
||||||
|
insert_final_newline = false
|
||||||
|
ij_kotlin_line_break_after_multiline_when_entry = false
|
||||||
|
|
||||||
|
[*.{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
|
||||||
|
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_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'
|
java-version: '17'
|
||||||
cache: gradle
|
cache: gradle
|
||||||
- name: Install klint
|
- name: Install klint
|
||||||
run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.0.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
|
run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.5.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
|
||||||
- name: Install detekt
|
- name: Install detekt
|
||||||
run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.1/detekt-cli-1.23.1.zip && unzip detekt-cli-1.23.1.zip
|
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
|
||||||
- name: Linting...
|
- name: Linting...
|
||||||
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build' || true
|
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build'
|
||||||
- name: Detecting...
|
- name: Detecting...
|
||||||
run: ./detekt-cli-1.23.1/bin/detekt-cli --all-rules --excludes '**/shared/build/**/*.kt' || true
|
run: ./detekt-cli-1.23.7/bin/detekt-cli -c detekt.yml --excludes '**/shared/build/**/*.kt'
|
||||||
build:
|
build:
|
||||||
needs: Lint
|
needs: Lint
|
||||||
uses: ./.gitea/workflows/common_build.yml
|
uses: ./.gitea/workflows/common_build.yml
|
@ -20,65 +20,72 @@ import org.hamcrest.Matchers.hasToString
|
|||||||
fun performLogin(someUrl: String? = null) {
|
fun performLogin(someUrl: String? = null) {
|
||||||
onView(withId(R.id.urlView)).perform(click()).perform(
|
onView(withId(R.id.urlView)).perform(click()).perform(
|
||||||
typeTextIntoFocusedView(
|
typeTextIntoFocusedView(
|
||||||
if (!someUrl.isNullOrEmpty()) someUrl else "http://10.0.2.2:8888"
|
if (!someUrl.isNullOrEmpty()) someUrl else "http://10.0.2.2:8888",
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withId(R.id.signInButton)).perform(click())
|
onView(withId(R.id.signInButton)).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loginAndInitHome() {
|
fun loginAndInitHome() {
|
||||||
|
|
||||||
performLogin()
|
performLogin()
|
||||||
onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed()))
|
onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed()))
|
||||||
onView(withText("OK")).perform(click())
|
onView(withText("OK")).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeAndCancelSetting(oldValue: String, newValue: String, openSettingItem: () -> Unit) {
|
fun changeAndCancelSetting(
|
||||||
|
oldValue: String,
|
||||||
|
newValue: String,
|
||||||
|
openSettingItem: () -> Unit,
|
||||||
|
) {
|
||||||
openSettingItem()
|
openSettingItem()
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.edit)
|
withId(android.R.id.edit),
|
||||||
).perform(replaceText(newValue))
|
).perform(replaceText(newValue))
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.button2)
|
withId(android.R.id.button2),
|
||||||
).perform(click())
|
).perform(click())
|
||||||
openSettingItem()
|
openSettingItem()
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.edit)
|
withId(android.R.id.edit),
|
||||||
).check(matches(withText(oldValue)))
|
).check(matches(withText(oldValue)))
|
||||||
onView(
|
onView(
|
||||||
withText(newValue)
|
withText(newValue),
|
||||||
).check(doesNotExist())
|
).check(doesNotExist())
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.button2)
|
withId(android.R.id.button2),
|
||||||
).perform(click())
|
).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeAndSaveSetting(oldValue: String, newValue: String, openSettingItem: () -> Unit) {
|
fun changeAndSaveSetting(
|
||||||
|
oldValue: String,
|
||||||
|
newValue: String,
|
||||||
|
openSettingItem: () -> Unit,
|
||||||
|
) {
|
||||||
openSettingItem()
|
openSettingItem()
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.edit)
|
withId(android.R.id.edit),
|
||||||
).perform(replaceText(newValue))
|
).perform(replaceText(newValue))
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.button1)
|
withId(android.R.id.button1),
|
||||||
).perform(click())
|
).perform(click())
|
||||||
openSettingItem()
|
openSettingItem()
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.edit)
|
withId(android.R.id.edit),
|
||||||
).check(matches(withText(newValue)))
|
).check(matches(withText(newValue)))
|
||||||
if (oldValue.isNotEmpty()) {
|
if (oldValue.isNotEmpty()) {
|
||||||
onView(
|
onView(
|
||||||
withText(oldValue)
|
withText(oldValue),
|
||||||
).check(doesNotExist())
|
).check(doesNotExist())
|
||||||
}
|
}
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.button2)
|
withId(android.R.id.button2),
|
||||||
).perform(click())
|
).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testPreferencesFromArray(
|
fun testPreferencesFromArray(
|
||||||
context: Context,
|
context: Context,
|
||||||
@ArrayRes arrayRes: Int,
|
@ArrayRes arrayRes: Int,
|
||||||
openSettingItem: () -> Unit
|
openSettingItem: () -> Unit,
|
||||||
) {
|
) {
|
||||||
openSettingItem()
|
openSettingItem()
|
||||||
context.resources.getStringArray(arrayRes).forEach { res ->
|
context.resources.getStringArray(arrayRes).forEach { res ->
|
||||||
@ -90,16 +97,21 @@ fun testPreferencesFromArray(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testAddSourceWithUrl(url: String, sourceName: String) {
|
fun testAddSourceWithUrl(
|
||||||
|
url: String,
|
||||||
|
sourceName: String,
|
||||||
|
) {
|
||||||
onView(withId(R.id.fab))
|
onView(withId(R.id.fab))
|
||||||
.perform(click())
|
.perform(click())
|
||||||
onView(withId(R.id.nameInput))
|
onView(withId(R.id.nameInput))
|
||||||
.perform(click()).perform(typeTextIntoFocusedView(sourceName))
|
.perform(click())
|
||||||
|
.perform(typeTextIntoFocusedView(sourceName))
|
||||||
onView(withId(R.id.sourceUri))
|
onView(withId(R.id.sourceUri))
|
||||||
.perform(click())
|
.perform(click())
|
||||||
.perform(typeTextIntoFocusedView(url))
|
.perform(typeTextIntoFocusedView(url))
|
||||||
onView(withId(R.id.tags))
|
onView(withId(R.id.tags))
|
||||||
.perform(click()).perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
|
.perform(click())
|
||||||
|
.perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
|
||||||
onView(withId(R.id.spoutsSpinner))
|
onView(withId(R.id.spoutsSpinner))
|
||||||
.perform(click())
|
.perform(click())
|
||||||
onData(hasToString("RSS Feed")).perform(click())
|
onData(hasToString("RSS Feed")).perform(click())
|
||||||
|
@ -25,38 +25,35 @@ import org.hamcrest.Matcher
|
|||||||
import org.hamcrest.Matchers
|
import org.hamcrest.Matchers
|
||||||
import org.hamcrest.TypeSafeMatcher
|
import org.hamcrest.TypeSafeMatcher
|
||||||
|
|
||||||
|
fun withError(
|
||||||
fun withError(@StringRes id: Int): TypeSafeMatcher<View?> {
|
@StringRes id: Int,
|
||||||
|
): TypeSafeMatcher<View?> {
|
||||||
return object : TypeSafeMatcher<View?>() {
|
return object : TypeSafeMatcher<View?>() {
|
||||||
override fun matchesSafely(view: View?): Boolean {
|
override fun matchesSafely(view: View?): Boolean {
|
||||||
if (view == null) {
|
if (view != null && (view !is EditText || view.error == null)) {
|
||||||
return false
|
|
||||||
}
|
|
||||||
val context = view.context
|
|
||||||
if (view !is EditText) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (view.error == null) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
val context = view!!.context
|
||||||
|
|
||||||
return view.error.toString() == context.getString(id)
|
return (view as EditText).error.toString() == context.getString(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun describeTo(description: Description?) {
|
override fun describeTo(description: Description?) {
|
||||||
|
// Nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPopupWindow(): Matcher<Root> {
|
fun isPopupWindow(): Matcher<Root> = isPlatformPopup()
|
||||||
return isPlatformPopup()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun withDrawable(@DrawableRes id: Int) = object : TypeSafeMatcher<View>() {
|
fun withDrawable(
|
||||||
|
@DrawableRes id: Int,
|
||||||
|
) = object : TypeSafeMatcher<View>() {
|
||||||
override fun describeTo(description: Description) {
|
override fun describeTo(description: Description) {
|
||||||
description.appendText("ImageView with drawable same as drawable with id $id")
|
description.appendText("ImageView with drawable same as drawable with id $id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:SwallowedException")
|
||||||
override fun matchesSafely(view: View): Boolean {
|
override fun matchesSafely(view: View): Boolean {
|
||||||
val context = view.context
|
val context = view.context
|
||||||
val expectedBitmap = context.getDrawable(id)!!.toBitmap()
|
val expectedBitmap = context.getDrawable(id)!!.toBitmap()
|
||||||
@ -68,43 +65,46 @@ fun withDrawable(@DrawableRes id: Int) = object : TypeSafeMatcher<View>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasBottombarItemText(@StringRes id: Int): Matcher<View>? {
|
fun hasBottombarItemText(
|
||||||
return allOf(
|
@StringRes id: Int,
|
||||||
|
): Matcher<View>? =
|
||||||
|
allOf(
|
||||||
withResourceName("fixed_bottom_navigation_icon"),
|
withResourceName("fixed_bottom_navigation_icon"),
|
||||||
withParent(
|
withParent(
|
||||||
allOf(
|
allOf(
|
||||||
withResourceName("fixed_bottom_navigation_icon_container"),
|
withResourceName("fixed_bottom_navigation_icon_container"),
|
||||||
hasSibling(withText(id))
|
hasSibling(withText(id)),
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
fun withSettingsCheckboxWidget(@StringRes id: Int): Matcher<View>? {
|
fun withSettingsCheckboxWidget(
|
||||||
return allOf(
|
@StringRes id: Int,
|
||||||
|
): Matcher<View>? =
|
||||||
|
allOf(
|
||||||
withId(android.R.id.switch_widget),
|
withId(android.R.id.switch_widget),
|
||||||
withParent(
|
withParent(
|
||||||
withSettingsCheckboxFrame(id)
|
withSettingsCheckboxFrame(id),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
fun withSettingsCheckboxFrame(@StringRes id: Int): Matcher<View>? {
|
fun withSettingsCheckboxFrame(
|
||||||
return allOf(
|
@StringRes id: Int,
|
||||||
|
): Matcher<View>? =
|
||||||
|
allOf(
|
||||||
withId(android.R.id.widget_frame),
|
withId(android.R.id.widget_frame),
|
||||||
hasSibling(
|
hasSibling(
|
||||||
allOf(
|
allOf(
|
||||||
withClassName(Matchers.equalTo(RelativeLayout::class.java.name)),
|
withClassName(Matchers.equalTo(RelativeLayout::class.java.name)),
|
||||||
withChild(
|
withChild(
|
||||||
withText(id)
|
withText(id),
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
fun openMenu() {
|
fun openMenu() {
|
||||||
openActionBarOverflowOrOptionsMenu(
|
openActionBarOverflowOrOptionsMenu(
|
||||||
ApplicationProvider.getApplicationContext<Context>()
|
ApplicationProvider.getApplicationContext<Context>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -23,7 +23,6 @@ import org.junit.runner.RunWith
|
|||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
class HomeActivityTest {
|
class HomeActivityTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
@ -36,13 +35,13 @@ class HomeActivityTest {
|
|||||||
fun testMenu() {
|
fun testMenu() {
|
||||||
onView(withId(R.id.action_search)).check(matches(isDisplayed())).check(
|
onView(withId(R.id.action_search)).check(matches(isDisplayed())).check(
|
||||||
matches(
|
matches(
|
||||||
isClickable()
|
isClickable(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withId(R.id.action_filter)).check(matches(isDisplayed())).check(
|
onView(withId(R.id.action_filter)).check(matches(isDisplayed())).check(
|
||||||
matches(
|
matches(
|
||||||
isClickable()
|
isClickable(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
openMenu()
|
openMenu()
|
||||||
onView(withText(R.string.readAll)).check(matches(isDisplayed()))
|
onView(withText(R.string.readAll)).check(matches(isDisplayed()))
|
||||||
@ -57,19 +56,19 @@ class HomeActivityTest {
|
|||||||
fun testMenuActions() {
|
fun testMenuActions() {
|
||||||
onView(withId(R.id.action_search)).perform(click())
|
onView(withId(R.id.action_search)).perform(click())
|
||||||
onView(
|
onView(
|
||||||
withId(R.id.search_src_text)
|
withId(R.id.search_src_text),
|
||||||
).check(matches(isFocused()))
|
).check(matches(isFocused()))
|
||||||
onView(isRoot()).perform(ViewActions.pressBack())
|
onView(isRoot()).perform(ViewActions.pressBack())
|
||||||
|
|
||||||
onView(withId(R.id.action_filter)).perform(click())
|
onView(withId(R.id.action_filter)).perform(click())
|
||||||
onView(
|
onView(
|
||||||
withText(R.string.filter_item_sources)
|
withText(R.string.filter_item_sources),
|
||||||
).check(matches(isDisplayed()))
|
).check(matches(isDisplayed()))
|
||||||
onView(
|
onView(
|
||||||
withText(R.string.filter_item_tags)
|
withText(R.string.filter_item_tags),
|
||||||
).check(matches(isDisplayed()))
|
).check(matches(isDisplayed()))
|
||||||
onView(
|
onView(
|
||||||
withId(R.id.floatingActionButton2)
|
withId(R.id.floatingActionButton2),
|
||||||
).check(matches(isDisplayed()))
|
).check(matches(isDisplayed()))
|
||||||
onView(isRoot()).perform(ViewActions.pressBack())
|
onView(isRoot()).perform(ViewActions.pressBack())
|
||||||
|
|
||||||
@ -107,14 +106,13 @@ class HomeActivityTest {
|
|||||||
fun testEmptyView() {
|
fun testEmptyView() {
|
||||||
onView(withId(R.id.emptyText)).check(matches(isDisplayed()))
|
onView(withId(R.id.emptyText)).check(matches(isDisplayed()))
|
||||||
onView(
|
onView(
|
||||||
hasBottombarItemText(R.string.tab_new)
|
hasBottombarItemText(R.string.tab_new),
|
||||||
).check(matches(isDisplayed())).check(matches(isSelected()))
|
).check(matches(isDisplayed())).check(matches(isSelected()))
|
||||||
onView(
|
onView(
|
||||||
hasBottombarItemText(R.string.tab_read)
|
hasBottombarItemText(R.string.tab_read),
|
||||||
).check(matches(isDisplayed())).check(matches(not(isSelected())))
|
).check(matches(isDisplayed())).check(matches(not(isSelected())))
|
||||||
onView(
|
onView(
|
||||||
hasBottombarItemText(R.string.tab_favs)
|
hasBottombarItemText(R.string.tab_favs),
|
||||||
).check(matches(isDisplayed())).check(matches(not(isSelected())))
|
).check(matches(isDisplayed())).check(matches(not(isSelected())))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android
|
package bou.amine.apps.readerforselfossv2.android
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import androidx.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.IdlingRegistry
|
import androidx.test.espresso.IdlingRegistry
|
||||||
import androidx.test.espresso.action.ViewActions.click
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
@ -23,40 +22,37 @@ import org.junit.runner.RunWith
|
|||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
class LoginActivityTest {
|
class LoginActivityTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
private fun getActivity(): Activity? {
|
|
||||||
var activity: Activity? = null
|
|
||||||
activityRule.scenario.onActivity {
|
|
||||||
activity = it
|
|
||||||
}
|
|
||||||
return activity
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun registerIdlingResource() {
|
fun registerIdlingResource() {
|
||||||
IdlingRegistry.getInstance()
|
IdlingRegistry
|
||||||
|
.getInstance()
|
||||||
.register(CountingIdlingResourceSingleton.countingIdlingResource)
|
.register(CountingIdlingResourceSingleton.countingIdlingResource)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun unregisterIdlingResource() {
|
fun unregisterIdlingResource() {
|
||||||
IdlingRegistry.getInstance()
|
IdlingRegistry
|
||||||
|
.getInstance()
|
||||||
.unregister(CountingIdlingResourceSingleton.countingIdlingResource)
|
.unregister(CountingIdlingResourceSingleton.countingIdlingResource)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun viewIsInitialized() {
|
fun viewIsInitialized() {
|
||||||
onView(withId(R.id.urlView)).check(matches(isDisplayed()))
|
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(
|
.check(
|
||||||
matches(isClickable())
|
matches(isClickable()),
|
||||||
)
|
)
|
||||||
onView(withId(R.id.withLogin)).check(matches(isDisplayed()))
|
onView(withId(R.id.withLogin))
|
||||||
.check(matches(isNotChecked())).check(
|
.check(matches(isDisplayed()))
|
||||||
matches(isClickable())
|
.check(matches(isNotChecked()))
|
||||||
|
.check(
|
||||||
|
matches(isClickable()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,11 +26,9 @@ import org.junit.Rule
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
class SettingsActivityGeneralTest {
|
class SettingsActivityGeneralTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
@ -38,81 +36,90 @@ class SettingsActivityGeneralTest {
|
|||||||
fun init() {
|
fun init() {
|
||||||
loginAndInitHome()
|
loginAndInitHome()
|
||||||
openActionBarOverflowOrOptionsMenu(
|
openActionBarOverflowOrOptionsMenu(
|
||||||
ApplicationProvider.getApplicationContext()
|
ApplicationProvider.getApplicationContext(),
|
||||||
)
|
)
|
||||||
onView(withText(R.string.title_activity_settings)).perform(click())
|
onView(withText(R.string.title_activity_settings)).perform(click())
|
||||||
onView(withText(R.string.pref_header_general)).perform(click())
|
onView(withText(R.string.pref_header_general)).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:LongMethod")
|
||||||
@Test
|
@Test
|
||||||
fun testGeneral() {
|
fun testGeneral() {
|
||||||
onView(withText(R.string.pref_api_items_number_title)).check(matches(isDisplayed()))
|
onView(withText(R.string.pref_api_items_number_title)).check(matches(isDisplayed()))
|
||||||
onView(
|
onView(
|
||||||
withSettingsCheckboxWidget(R.string.pref_general_infinite_loading_title)
|
withSettingsCheckboxWidget(R.string.pref_general_infinite_loading_title),
|
||||||
).check(
|
).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(), not(isChecked())
|
isDisplayed(),
|
||||||
)
|
not(isChecked()),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
onView(withText(R.string.pref_general_category_links)).check(matches(isDisplayed()))
|
onView(withText(R.string.pref_general_category_links)).check(matches(isDisplayed()))
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_article_viewer_title)).check(
|
onView(withSettingsCheckboxWidget(R.string.pref_article_viewer_title)).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(), isChecked()
|
isDisplayed(),
|
||||||
)
|
isChecked(),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxWidget(R.string.reader_static_bar_title)).check(
|
onView(withSettingsCheckboxWidget(R.string.reader_static_bar_title)).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(), not(isChecked())
|
isDisplayed(),
|
||||||
)
|
not(isChecked()),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||||
matches(
|
matches(
|
||||||
isEnabled()
|
isEnabled(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withText(R.string.pref_general_category_displaying)).check(matches(isDisplayed()))
|
onView(withText(R.string.pref_general_category_displaying)).check(matches(isDisplayed()))
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).check(
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(), not(isChecked())
|
isDisplayed(),
|
||||||
)
|
not(isChecked()),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxWidget(R.string.card_height_title)).check(
|
onView(withSettingsCheckboxWidget(R.string.card_height_title)).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(), not(isChecked())
|
isDisplayed(),
|
||||||
)
|
not(isChecked()),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(
|
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(
|
||||||
matches(
|
matches(
|
||||||
not(isEnabled())
|
not(isEnabled()),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxWidget(R.string.switch_unread_count_title)).check(
|
onView(withSettingsCheckboxWidget(R.string.switch_unread_count_title)).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(), isChecked()
|
isDisplayed(),
|
||||||
)
|
isChecked(),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
onView(withId(R.id.settings)).perform(swipeUp())
|
onView(withId(R.id.settings)).perform(swipeUp())
|
||||||
onView(withSettingsCheckboxWidget(R.string.display_all_counts_title)).check(
|
onView(withSettingsCheckboxWidget(R.string.display_all_counts_title)).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(), not(isChecked())
|
isDisplayed(),
|
||||||
)
|
not(isChecked()),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:ForbiddenComment")
|
||||||
@Test
|
@Test
|
||||||
fun testGeneralActionsNumberItems() {
|
fun testGeneralActionsNumberItems() {
|
||||||
onView(withText(R.string.pref_api_items_number_title)).perform(click())
|
onView(withText(R.string.pref_api_items_number_title)).perform(click())
|
||||||
@ -120,25 +127,25 @@ class SettingsActivityGeneralTest {
|
|||||||
|
|
||||||
// Value check
|
// Value check
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.edit)
|
withId(android.R.id.edit),
|
||||||
).perform(replaceText("AVC"))
|
).perform(replaceText("AVC"))
|
||||||
.check(matches(withText("")))
|
.check(matches(withText("")))
|
||||||
// TODO: should check message error. Not working for api level 30+
|
// TODO: should check message error. Not working for api level 30+
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.edit)
|
withId(android.R.id.edit),
|
||||||
).perform(replaceText("-1"))
|
).perform(replaceText("-1"))
|
||||||
.check(matches(withText("")))
|
.check(matches(withText("")))
|
||||||
// TODO: should check message error. Not working for api level 30+
|
// TODO: should check message error. Not working for api level 30+
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.edit)
|
withId(android.R.id.edit),
|
||||||
).perform(replaceText("300"))
|
).perform(replaceText("300"))
|
||||||
.check(matches(withText("")))
|
.check(matches(withText("")))
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.edit)
|
withId(android.R.id.edit),
|
||||||
).perform(typeTextIntoFocusedView("300"))
|
).perform(typeTextIntoFocusedView("300"))
|
||||||
.check(matches(withText("30")))
|
.check(matches(withText("30")))
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.edit)
|
withId(android.R.id.edit),
|
||||||
).perform(replaceText("10"))
|
).perform(replaceText("10"))
|
||||||
.check(matches(withText("10")))
|
.check(matches(withText("10")))
|
||||||
onView(isRoot()).perform(ViewActions.pressBack())
|
onView(isRoot()).perform(ViewActions.pressBack())
|
||||||
@ -157,14 +164,14 @@ class SettingsActivityGeneralTest {
|
|||||||
// article viewer settings
|
// article viewer settings
|
||||||
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||||
matches(
|
matches(
|
||||||
isEnabled()
|
isEnabled(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_article_viewer_title)).perform(click())
|
onView(withSettingsCheckboxWidget(R.string.pref_article_viewer_title)).perform(click())
|
||||||
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||||
matches(
|
matches(
|
||||||
not(isEnabled())
|
not(isEnabled()),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(not(isEnabled())))
|
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(not(isEnabled())))
|
||||||
|
@ -21,11 +21,9 @@ import org.junit.Rule
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
class SettingsActivityOfflineTest {
|
class SettingsActivityOfflineTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
@ -38,72 +36,79 @@ class SettingsActivityOfflineTest {
|
|||||||
}
|
}
|
||||||
loginAndInitHome()
|
loginAndInitHome()
|
||||||
openActionBarOverflowOrOptionsMenu(
|
openActionBarOverflowOrOptionsMenu(
|
||||||
ApplicationProvider.getApplicationContext()
|
ApplicationProvider.getApplicationContext(),
|
||||||
)
|
)
|
||||||
onView(withText(R.string.title_activity_settings)).perform(click())
|
onView(withText(R.string.title_activity_settings)).perform(click())
|
||||||
onView(withText(R.string.pref_header_offline)).perform(click())
|
onView(withText(R.string.pref_header_offline)).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:LongMethod")
|
||||||
@Test
|
@Test
|
||||||
fun testOffline() {
|
fun testOffline() {
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).check(
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(), not(isChecked())
|
isDisplayed(),
|
||||||
)
|
not(isChecked()),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_items_caching)).check(
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_items_caching)).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(), not(isChecked())
|
isDisplayed(),
|
||||||
)
|
not(isChecked()),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_items_caching)).check(
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_items_caching)).check(
|
||||||
matches(
|
matches(
|
||||||
isEnabled()
|
isEnabled(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withText(R.string.pref_periodic_refresh_minutes_title)).check(
|
onView(withText(R.string.pref_periodic_refresh_minutes_title)).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(isNotEnabled(), isDisplayed())
|
allOf(isNotEnabled(), isDisplayed()),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_refresh_when_charging)).check(
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_refresh_when_charging)).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(), not(isChecked())
|
isDisplayed(),
|
||||||
)
|
not(isChecked()),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
|
||||||
matches(
|
matches(
|
||||||
isNotEnabled()
|
isNotEnabled(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_notify_new_items)).check(
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_notify_new_items)).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(), not(isChecked())
|
isDisplayed(),
|
||||||
)
|
not(isChecked()),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
|
||||||
matches(
|
matches(
|
||||||
isNotEnabled()
|
isNotEnabled(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_update_sources)).check(
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_update_sources)).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(), isChecked()
|
isDisplayed(),
|
||||||
)
|
isChecked(),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:LongMethod")
|
||||||
@Test
|
@Test
|
||||||
fun testOfflineActions() {
|
fun testOfflineActions() {
|
||||||
onView(withText(R.string.pref_switch_items_caching_off)).check(matches(isDisplayed()))
|
onView(withText(R.string.pref_switch_items_caching_off)).check(matches(isDisplayed()))
|
||||||
@ -111,50 +116,50 @@ class SettingsActivityOfflineTest {
|
|||||||
onView(withText(R.string.pref_switch_items_caching_on)).check(matches(isDisplayed()))
|
onView(withText(R.string.pref_switch_items_caching_on)).check(matches(isDisplayed()))
|
||||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_items_caching)).check(
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_items_caching)).check(
|
||||||
matches(
|
matches(
|
||||||
isEnabled()
|
isEnabled(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withText(R.string.pref_periodic_refresh_minutes_title)).check(
|
onView(withText(R.string.pref_periodic_refresh_minutes_title)).check(
|
||||||
matches(
|
matches(
|
||||||
isNotEnabled()
|
isNotEnabled(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
|
||||||
matches(
|
matches(
|
||||||
isNotEnabled()
|
isNotEnabled(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
|
||||||
matches(
|
matches(
|
||||||
isNotEnabled()
|
isNotEnabled(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
onView(withText(R.string.pref_switch_periodic_refresh_off)).check(
|
onView(withText(R.string.pref_switch_periodic_refresh_off)).check(
|
||||||
matches(
|
matches(
|
||||||
isDisplayed()
|
isDisplayed(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).perform(click())
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).perform(click())
|
||||||
onView(withText(R.string.pref_switch_periodic_refresh_on)).check(
|
onView(withText(R.string.pref_switch_periodic_refresh_on)).check(
|
||||||
matches(
|
matches(
|
||||||
isDisplayed()
|
isDisplayed(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxFrame(R.string.pref_periodic_refresh_minutes_title)).check(
|
onView(withSettingsCheckboxFrame(R.string.pref_periodic_refresh_minutes_title)).check(
|
||||||
matches(
|
matches(
|
||||||
isEnabled()
|
isEnabled(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
|
||||||
matches(
|
matches(
|
||||||
isEnabled()
|
isEnabled(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
|
||||||
matches(
|
matches(
|
||||||
isEnabled()
|
isEnabled(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
changeAndCancelSetting("360", "123") {
|
changeAndCancelSetting("360", "123") {
|
||||||
onView(withText(R.string.pref_periodic_refresh_minutes_title)).perform(click())
|
onView(withText(R.string.pref_periodic_refresh_minutes_title)).perform(click())
|
||||||
|
@ -19,11 +19,9 @@ import org.junit.Rule
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
class SettingsActivityReaderTest {
|
class SettingsActivityReaderTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
@ -36,7 +34,7 @@ class SettingsActivityReaderTest {
|
|||||||
}
|
}
|
||||||
loginAndInitHome()
|
loginAndInitHome()
|
||||||
openActionBarOverflowOrOptionsMenu(
|
openActionBarOverflowOrOptionsMenu(
|
||||||
ApplicationProvider.getApplicationContext()
|
ApplicationProvider.getApplicationContext(),
|
||||||
)
|
)
|
||||||
onView(withText(R.string.title_activity_settings)).perform(click())
|
onView(withText(R.string.title_activity_settings)).perform(click())
|
||||||
onView(withText(R.string.pref_header_viewer)).perform(click())
|
onView(withText(R.string.pref_header_viewer)).perform(click())
|
||||||
@ -47,11 +45,12 @@ class SettingsActivityReaderTest {
|
|||||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_actions_pager_scroll)).check(
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_actions_pager_scroll)).check(
|
||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(), not(
|
isDisplayed(),
|
||||||
isChecked()
|
not(
|
||||||
)
|
isChecked(),
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
onView(withText(R.string.pref_content_reader_font_size)).check(matches(isDisplayed()))
|
onView(withText(R.string.pref_content_reader_font_size)).check(matches(isDisplayed()))
|
||||||
onView(withText(R.string.settings_reader_font)).check(matches(isDisplayed()))
|
onView(withText(R.string.settings_reader_font)).check(matches(isDisplayed()))
|
||||||
@ -61,14 +60,14 @@ class SettingsActivityReaderTest {
|
|||||||
fun testReaderActions() {
|
fun testReaderActions() {
|
||||||
onView(withText(R.string.pref_switch_actions_pager_scroll_off)).check(
|
onView(withText(R.string.pref_switch_actions_pager_scroll_off)).check(
|
||||||
matches(
|
matches(
|
||||||
isDisplayed()
|
isDisplayed(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_actions_pager_scroll)).perform(click())
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_actions_pager_scroll)).perform(click())
|
||||||
onView(withText(R.string.pref_switch_actions_pager_scroll_on)).check(
|
onView(withText(R.string.pref_switch_actions_pager_scroll_on)).check(
|
||||||
matches(
|
matches(
|
||||||
isDisplayed()
|
isDisplayed(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
onView(withText(R.string.pref_content_reader_font_size)).perform(click())
|
onView(withText(R.string.pref_content_reader_font_size)).perform(click())
|
||||||
|
@ -20,7 +20,6 @@ import org.junit.runner.RunWith
|
|||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
class SettingsActivityTest {
|
class SettingsActivityTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
lateinit var context: Context
|
lateinit var context: Context
|
||||||
@ -35,10 +34,8 @@ class SettingsActivityTest {
|
|||||||
onView(withText(R.string.title_activity_settings)).perform(click())
|
onView(withText(R.string.title_activity_settings)).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testAllSettings() {
|
fun testAllSettings() {
|
||||||
|
|
||||||
onView(withText(R.string.pref_header_general)).check(matches(isDisplayed()))
|
onView(withText(R.string.pref_header_general)).check(matches(isDisplayed()))
|
||||||
onView(withText(R.string.pref_header_viewer)).check(matches(isDisplayed()))
|
onView(withText(R.string.pref_header_viewer)).check(matches(isDisplayed()))
|
||||||
onView(withText(R.string.pref_header_offline)).check(matches(isDisplayed()))
|
onView(withText(R.string.pref_header_offline)).check(matches(isDisplayed()))
|
||||||
@ -48,14 +45,13 @@ class SettingsActivityTest {
|
|||||||
matches(
|
matches(
|
||||||
allOf(
|
allOf(
|
||||||
isDisplayed(),
|
isDisplayed(),
|
||||||
not(isSelected())
|
not(isSelected()),
|
||||||
)
|
),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
onView(withText(R.string.action_about)).check(matches(isDisplayed()))
|
onView(withText(R.string.action_about)).check(matches(isDisplayed()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testThemes() {
|
fun testThemes() {
|
||||||
testPreferencesFromArray(context, R.array.ModeTitles) {
|
testPreferencesFromArray(context, R.array.ModeTitles) {
|
||||||
@ -63,7 +59,6 @@ class SettingsActivityTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testExperimentail() {
|
fun testExperimentail() {
|
||||||
onView(withText(R.string.pref_header_experimental)).perform(click())
|
onView(withText(R.string.pref_header_experimental)).perform(click())
|
||||||
@ -75,13 +70,11 @@ class SettingsActivityTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testBugReports() {
|
fun testBugReports() {
|
||||||
onView(withText(R.string.pref_switch_disable_acra)).perform(click())
|
onView(withText(R.string.pref_switch_disable_acra)).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLinks() {
|
fun testLinks() {
|
||||||
onView(withText(R.string.pref_header_links)).perform(click())
|
onView(withText(R.string.pref_header_links)).perform(click())
|
||||||
@ -91,7 +84,6 @@ class SettingsActivityTest {
|
|||||||
onView(withText(R.string.translation)).check(matches(isDisplayed()))
|
onView(withText(R.string.translation)).check(matches(isDisplayed()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testAbout() {
|
fun testAbout() {
|
||||||
onView(withText(R.string.action_about)).perform(click())
|
onView(withText(R.string.action_about)).perform(click())
|
||||||
|
@ -41,11 +41,11 @@ class SourcesActivityTest {
|
|||||||
fun addSource() {
|
fun addSource() {
|
||||||
testAddSourceWithUrl(
|
testAddSourceWithUrl(
|
||||||
"https://lorem-rss.herokuapp.com/feed?unit=year&interval=1&length=10",
|
"https://lorem-rss.herokuapp.com/feed?unit=year&interval=1&length=10",
|
||||||
sourceName
|
sourceName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:SwallowedException")
|
||||||
@Test
|
@Test
|
||||||
fun addSourceCheckContent() {
|
fun addSourceCheckContent() {
|
||||||
testAddSourceWithUrl("https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en", sourceName)
|
testAddSourceWithUrl("https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en", sourceName)
|
||||||
@ -54,7 +54,7 @@ class SourcesActivityTest {
|
|||||||
onView(withText(R.string.menu_home_refresh)).perform(click())
|
onView(withText(R.string.menu_home_refresh)).perform(click())
|
||||||
onView(withText(R.string.refresh_dialog_message)).check(matches(isDisplayed()))
|
onView(withText(R.string.refresh_dialog_message)).check(matches(isDisplayed()))
|
||||||
onView(
|
onView(
|
||||||
withId(android.R.id.button1)
|
withId(android.R.id.button1),
|
||||||
).perform(click())
|
).perform(click())
|
||||||
Thread.sleep(10000)
|
Thread.sleep(10000)
|
||||||
onView(withId(R.id.swipeRefreshLayout)).perform(swipeDown())
|
onView(withId(R.id.swipeRefreshLayout)).perform(swipeDown())
|
||||||
@ -74,7 +74,6 @@ class SourcesActivityTest {
|
|||||||
onView(withText(sourceName)).check(doesNotExist())
|
onView(withText(sourceName)).check(doesNotExist())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun goToSources() {
|
private fun goToSources() {
|
||||||
openMenu()
|
openMenu()
|
||||||
onView(withText(R.string.menu_home_sources))
|
onView(withText(R.string.menu_home_sources))
|
||||||
|
@ -49,7 +49,12 @@ import org.kodein.di.instance
|
|||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware {
|
private const val MIN_WIDTH_CARD_DP = 300
|
||||||
|
|
||||||
|
class HomeActivity :
|
||||||
|
AppCompatActivity(),
|
||||||
|
SearchView.OnQueryTextListener,
|
||||||
|
DIAware {
|
||||||
private var items: ArrayList<SelfossModel.Item> = ArrayList()
|
private var items: ArrayList<SelfossModel.Item> = ArrayList()
|
||||||
|
|
||||||
private var elementsShown: ItemType = ItemType.UNREAD
|
private var elementsShown: ItemType = ItemType.UNREAD
|
||||||
@ -171,11 +176,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this@HomeActivity,
|
.makeText(
|
||||||
"Found null when swiping at positon $position.",
|
this@HomeActivity,
|
||||||
Toast.LENGTH_LONG,
|
"Found null when swiping at positon $position.",
|
||||||
).show()
|
Toast.LENGTH_LONG,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,19 +202,23 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:LongMethod")
|
||||||
private fun handleBottomBar() {
|
private fun handleBottomBar() {
|
||||||
tabNewBadge =
|
tabNewBadge =
|
||||||
TextBadgeItem()
|
TextBadgeItem()
|
||||||
.setText("")
|
.setText("")
|
||||||
.setHideOnSelect(false).hide(false)
|
.setHideOnSelect(false)
|
||||||
|
.hide(false)
|
||||||
tabArchiveBadge =
|
tabArchiveBadge =
|
||||||
TextBadgeItem()
|
TextBadgeItem()
|
||||||
.setText("")
|
.setText("")
|
||||||
.setHideOnSelect(false).hide(false)
|
.setHideOnSelect(false)
|
||||||
|
.hide(false)
|
||||||
tabStarredBadge =
|
tabStarredBadge =
|
||||||
TextBadgeItem()
|
TextBadgeItem()
|
||||||
.setText("")
|
.setText("")
|
||||||
.setHideOnSelect(false).hide(false)
|
.setHideOnSelect(false)
|
||||||
|
.hide(false)
|
||||||
|
|
||||||
if (appSettingsService.isDisplayUnreadCountEnabled()) {
|
if (appSettingsService.isDisplayUnreadCountEnabled()) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
@ -236,14 +246,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
BottomNavigationItem(
|
BottomNavigationItem(
|
||||||
R.drawable.ic_tab_fiber_new_black_24dp,
|
R.drawable.ic_tab_fiber_new_black_24dp,
|
||||||
getString(R.string.tab_new),
|
getString(R.string.tab_new),
|
||||||
)
|
).setBadgeItem(tabNewBadge)
|
||||||
.setBadgeItem(tabNewBadge)
|
|
||||||
val tabArchive =
|
val tabArchive =
|
||||||
BottomNavigationItem(
|
BottomNavigationItem(
|
||||||
R.drawable.ic_tab_archive_black_24dp,
|
R.drawable.ic_tab_archive_black_24dp,
|
||||||
getString(R.string.tab_read),
|
getString(R.string.tab_read),
|
||||||
)
|
).setBadgeItem(tabArchiveBadge)
|
||||||
.setBadgeItem(tabArchiveBadge)
|
|
||||||
val tabStarred =
|
val tabStarred =
|
||||||
BottomNavigationItem(
|
BottomNavigationItem(
|
||||||
R.drawable.ic_tab_favorite_black_24dp,
|
R.drawable.ic_tab_favorite_black_24dp,
|
||||||
@ -277,7 +285,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
|
|
||||||
handleBottomBarActions()
|
handleBottomBarActions()
|
||||||
|
|
||||||
handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
|
handleGdprDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
|
||||||
|
|
||||||
handleRecurringTask()
|
handleRecurringTask()
|
||||||
CountingIdlingResourceSingleton.increment()
|
CountingIdlingResourceSingleton.increment()
|
||||||
@ -289,10 +297,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleGDPRDialog(GDPRShown: Boolean) {
|
private fun handleGdprDialog(gdprShown: Boolean) {
|
||||||
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
|
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
|
||||||
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
|
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
|
||||||
if (!GDPRShown) {
|
if (!gdprShown) {
|
||||||
val alertDialog = AlertDialog.Builder(this).create()
|
val alertDialog = AlertDialog.Builder(this).create()
|
||||||
alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
|
alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
|
||||||
alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
|
alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
|
||||||
@ -425,17 +433,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
binding.recyclerView.addOnScrollListener(recyclerViewScrollListener)
|
binding.recyclerView.addOnScrollListener(recyclerViewScrollListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLastVisibleItem(): Int {
|
private fun getLastVisibleItem(): Int =
|
||||||
return when (val manager = binding.recyclerView.layoutManager) {
|
when (val manager = binding.recyclerView.layoutManager) {
|
||||||
is StaggeredGridLayoutManager ->
|
is StaggeredGridLayoutManager ->
|
||||||
manager.findLastCompletelyVisibleItemPositions(
|
manager
|
||||||
null,
|
.findLastCompletelyVisibleItemPositions(
|
||||||
).last()
|
null,
|
||||||
|
).last()
|
||||||
|
|
||||||
is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition()
|
is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition()
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun mayBeEmpty() =
|
private fun mayBeEmpty() =
|
||||||
if (items.isEmpty()) {
|
if (items.isEmpty()) {
|
||||||
@ -538,7 +546,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
private fun calculateNoOfColumns(): Int {
|
private fun calculateNoOfColumns(): Int {
|
||||||
val displayMetrics = resources.displayMetrics
|
val displayMetrics = resources.displayMetrics
|
||||||
val dpWidth = displayMetrics.widthPixels / displayMetrics.density
|
val dpWidth = displayMetrics.widthPixels / displayMetrics.density
|
||||||
return (dpWidth / 300).toInt()
|
return (dpWidth / MIN_WIDTH_CARD_DP).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueryTextChange(p0: String?): Boolean {
|
override fun onQueryTextChange(p0: String?): Boolean {
|
||||||
@ -577,7 +585,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
messageRes: Int,
|
messageRes: Int,
|
||||||
doFn: () -> Unit,
|
doFn: () -> Unit,
|
||||||
) {
|
) {
|
||||||
AlertDialog.Builder(this@HomeActivity)
|
AlertDialog
|
||||||
|
.Builder(this@HomeActivity)
|
||||||
.setMessage(messageRes)
|
.setMessage(messageRes)
|
||||||
.setTitle(titleRes)
|
.setTitle(titleRes)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ -> doFn() }
|
.setPositiveButton(android.R.string.ok) { _, _ -> doFn() }
|
||||||
@ -586,10 +595,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:ReturnCount", "detekt:LongMethod")
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.issue_tracker -> {
|
R.id.issue_tracker -> {
|
||||||
baseContext.openUrlInBrowser(AppSettingsService.trackerUrl)
|
baseContext.openUrlInBrowser(AppSettingsService.BUG_URL)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -606,18 +616,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val updatedRemote = repository.updateRemote()
|
val updatedRemote = repository.updateRemote()
|
||||||
if (updatedRemote) {
|
if (updatedRemote) {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this@HomeActivity,
|
.makeText(
|
||||||
R.string.refresh_success_response,
|
this@HomeActivity,
|
||||||
Toast.LENGTH_LONG,
|
R.string.refresh_success_response,
|
||||||
)
|
Toast.LENGTH_LONG,
|
||||||
.show()
|
).show()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this@HomeActivity,
|
.makeText(
|
||||||
R.string.refresh_failer_message,
|
this@HomeActivity,
|
||||||
Toast.LENGTH_SHORT,
|
R.string.refresh_failer_message,
|
||||||
).show()
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
CountingIdlingResourceSingleton.decrement()
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
@ -633,25 +644,26 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val success = repository.markAllAsRead(items)
|
val success = repository.markAllAsRead(items)
|
||||||
if (success) {
|
if (success) {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this@HomeActivity,
|
.makeText(
|
||||||
R.string.all_posts_read,
|
this@HomeActivity,
|
||||||
Toast.LENGTH_SHORT,
|
R.string.all_posts_read,
|
||||||
).show()
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
tabNewBadge.removeBadge()
|
tabNewBadge.removeBadge()
|
||||||
|
|
||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this@HomeActivity,
|
.makeText(
|
||||||
R.string.all_posts_not_read,
|
this@HomeActivity,
|
||||||
Toast.LENGTH_SHORT,
|
R.string.all_posts_not_read,
|
||||||
).show()
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
handleListResult()
|
handleListResult()
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
CountingIdlingResourceSingleton.decrement()
|
CountingIdlingResourceSingleton.decrement()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -661,7 +673,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
R.id.action_disconnect -> {
|
R.id.action_disconnect -> {
|
||||||
needsConfirmation(
|
needsConfirmation(
|
||||||
R.string.confirm_disconnect_title,
|
R.string.confirm_disconnect_title,
|
||||||
R.string.confirm_disconnect_description
|
R.string.confirm_disconnect_description,
|
||||||
) {
|
) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
repository.logout()
|
repository.logout()
|
||||||
@ -702,7 +714,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
private fun handleRecurringTask() {
|
private fun handleRecurringTask() {
|
||||||
if (appSettingsService.isPeriodicRefreshEnabled()) {
|
if (appSettingsService.isPeriodicRefreshEnabled()) {
|
||||||
val myConstraints =
|
val myConstraints =
|
||||||
Constraints.Builder()
|
Constraints
|
||||||
|
.Builder()
|
||||||
.setRequiresBatteryNotLow(true)
|
.setRequiresBatteryNotLow(true)
|
||||||
.setRequiresCharging(appSettingsService.isRefreshWhenChargingOnlyEnabled())
|
.setRequiresCharging(appSettingsService.isRefreshWhenChargingOnlyEnabled())
|
||||||
.setRequiresStorageNotLow(true)
|
.setRequiresStorageNotLow(true)
|
||||||
@ -711,19 +724,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
val backgroundWork =
|
val backgroundWork =
|
||||||
PeriodicWorkRequestBuilder<LoadingWorker>(
|
PeriodicWorkRequestBuilder<LoadingWorker>(
|
||||||
appSettingsService.getRefreshMinutes(),
|
appSettingsService.getRefreshMinutes(),
|
||||||
TimeUnit.MINUTES
|
TimeUnit.MINUTES,
|
||||||
)
|
).setConstraints(myConstraints)
|
||||||
.setConstraints(myConstraints)
|
|
||||||
.addTag("selfoss-loading")
|
.addTag("selfoss-loading")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
WorkManager.getInstance(
|
WorkManager
|
||||||
baseContext,
|
.getInstance(
|
||||||
).enqueueUniquePeriodicWork(
|
baseContext,
|
||||||
"selfoss-loading",
|
).enqueueUniquePeriodicWork(
|
||||||
ExistingPeriodicWorkPolicy.KEEP,
|
"selfoss-loading",
|
||||||
backgroundWork
|
ExistingPeriodicWorkPolicy.KEEP,
|
||||||
)
|
backgroundWork,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -84,7 +84,9 @@ class ImageActivity : AppCompatActivity() {
|
|||||||
return super.onOptionsItemSelected(item)
|
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 getItemCount(): Int = allImages.size
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position])
|
override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position])
|
||||||
|
@ -30,7 +30,11 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
class LoginActivity : AppCompatActivity(), DIAware {
|
private const val MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED = 3
|
||||||
|
|
||||||
|
class LoginActivity :
|
||||||
|
AppCompatActivity(),
|
||||||
|
DIAware {
|
||||||
private var inValidCount: Int = 0
|
private var inValidCount: Int = 0
|
||||||
private var isWithLogin = false
|
private var isWithLogin = false
|
||||||
|
|
||||||
@ -108,7 +112,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
|||||||
repository.updateApiInformation()
|
repository.updateApiInformation()
|
||||||
ACRA.errorReporter.putCustomData(
|
ACRA.errorReporter.putCustomData(
|
||||||
"SELFOSS_API_VERSION",
|
"SELFOSS_API_VERSION",
|
||||||
appSettingsService.getApiVersion().toString()
|
appSettingsService.getApiVersion().toString(),
|
||||||
)
|
)
|
||||||
CountingIdlingResourceSingleton.decrement()
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
@ -132,9 +136,18 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
|||||||
binding.passwordView.error = null
|
binding.passwordView.error = null
|
||||||
|
|
||||||
// Store values at the time of the login attempt.
|
// Store values at the time of the login attempt.
|
||||||
val url = binding.urlView.text.toString().trim()
|
val url =
|
||||||
val login = binding.loginView.text.toString().trim()
|
binding.urlView.text
|
||||||
val password = binding.passwordView.text.toString().trim()
|
.toString()
|
||||||
|
.trim()
|
||||||
|
val login =
|
||||||
|
binding.loginView.text
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
val password =
|
||||||
|
binding.passwordView.text
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
|
||||||
failInvalidUrl(url)
|
failInvalidUrl(url)
|
||||||
failLoginDetails(password, login)
|
failLoginDetails(password, login)
|
||||||
@ -151,11 +164,12 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
|||||||
repository.updateApiInformation()
|
repository.updateApiInformation()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e.message?.startsWith("No transformation found") == true) {
|
if (e.message?.startsWith("No transformation found") == true) {
|
||||||
Toast.makeText(
|
Toast
|
||||||
applicationContext,
|
.makeText(
|
||||||
R.string.application_selfoss_only,
|
applicationContext,
|
||||||
Toast.LENGTH_LONG,
|
R.string.application_selfoss_only,
|
||||||
).show()
|
Toast.LENGTH_LONG,
|
||||||
|
).show()
|
||||||
preferenceError()
|
preferenceError()
|
||||||
showProgress(false)
|
showProgress(false)
|
||||||
}
|
}
|
||||||
@ -205,7 +219,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
|||||||
cancel = true
|
cancel = true
|
||||||
binding.urlView.error = getString(R.string.login_url_problem)
|
binding.urlView.error = getString(R.string.login_url_problem)
|
||||||
inValidCount++
|
inValidCount++
|
||||||
if (inValidCount == 3) {
|
if (inValidCount == MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED) {
|
||||||
val alertDialog = AlertDialog.Builder(this).create()
|
val alertDialog = AlertDialog.Builder(this).create()
|
||||||
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
||||||
alertDialog.setMessage(getString(R.string.text_wrong_url))
|
alertDialog.setMessage(getString(R.string.text_wrong_url))
|
||||||
@ -270,7 +284,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
|||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
R.id.issue_tracker -> {
|
R.id.issue_tracker -> {
|
||||||
val browserIntent =
|
val browserIntent =
|
||||||
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.trackerUrl))
|
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.BUG_URL))
|
||||||
startActivity(browserIntent)
|
startActivity(browserIntent)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -280,9 +294,9 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
|||||||
.withAboutIconShown(true)
|
.withAboutIconShown(true)
|
||||||
.withAboutVersionShown(true)
|
.withAboutVersionShown(true)
|
||||||
.withAboutSpecial2("Bug reports")
|
.withAboutSpecial2("Bug reports")
|
||||||
.withAboutSpecial2Description(AppSettingsService.trackerUrl)
|
.withAboutSpecial2Description(AppSettingsService.BUG_URL)
|
||||||
.withAboutSpecial1("Project Page")
|
.withAboutSpecial1("Project Page")
|
||||||
.withAboutSpecial1Description(AppSettingsService.sourceUrl)
|
.withAboutSpecial1Description(AppSettingsService.SOURCE_URL)
|
||||||
.start(this)
|
.start(this)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,11 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
|||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.multidex.MultiDexApplication
|
import androidx.multidex.MultiDexApplication
|
||||||
import bou.amine.apps.readerforselfossv2.DI.networkModule
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.testing.TestingHelper
|
import bou.amine.apps.readerforselfossv2.android.testing.TestingHelper
|
||||||
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
|
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
|
||||||
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
|
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
|
||||||
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
||||||
|
import bou.amine.apps.readerforselfossv2.di.networkModule
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import com.github.ln_12.library.ConnectivityStatus
|
import com.github.ln_12.library.ConnectivityStatus
|
||||||
@ -36,21 +36,23 @@ import org.kodein.di.bind
|
|||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import org.kodein.di.singleton
|
import org.kodein.di.singleton
|
||||||
|
|
||||||
class MyApp : MultiDexApplication(), DIAware {
|
class MyApp :
|
||||||
|
MultiDexApplication(),
|
||||||
|
DIAware {
|
||||||
override val di by DI.lazy {
|
override val di by DI.lazy {
|
||||||
bind<AppSettingsService>() with singleton { AppSettingsService(ACRA.isACRASenderServiceProcess() || TestingHelper().isUnitTest()) }
|
bind<AppSettingsService>() with singleton { AppSettingsService(ACRA.isACRASenderServiceProcess() || TestingHelper().isUnitTest()) }
|
||||||
import(networkModule)
|
import(networkModule)
|
||||||
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
|
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
|
||||||
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
|
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
|
||||||
bind<Repository>() with
|
bind<Repository>() with
|
||||||
singleton {
|
singleton {
|
||||||
Repository(
|
Repository(
|
||||||
instance(),
|
instance(),
|
||||||
instance(),
|
instance(),
|
||||||
isConnectionAvailable,
|
isConnectionAvailable,
|
||||||
instance(),
|
instance(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
|
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
|
||||||
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
|
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
|
||||||
}
|
}
|
||||||
@ -60,6 +62,7 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
private val connectivityStatus: ConnectivityStatus by instance()
|
private val connectivityStatus: ConnectivityStatus by instance()
|
||||||
private val driverFactory: DriverFactory by instance()
|
private val driverFactory: DriverFactory by instance()
|
||||||
|
|
||||||
|
@Suppress("detekt:ForbiddenComment")
|
||||||
// TODO: handle with the "previous" way
|
// TODO: handle with the "previous" way
|
||||||
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
||||||
|
|
||||||
@ -89,11 +92,12 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
R.string.network_connectivity_lost
|
R.string.network_connectivity_lost
|
||||||
}
|
}
|
||||||
|
|
||||||
Toast.makeText(
|
Toast
|
||||||
applicationContext,
|
.makeText(
|
||||||
toastMessage,
|
applicationContext,
|
||||||
Toast.LENGTH_SHORT,
|
toastMessage,
|
||||||
).show()
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,13 +155,13 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
|
|
||||||
val name = getString(R.string.notification_channel_sync)
|
val name = getString(R.string.notification_channel_sync)
|
||||||
val importance = NotificationManager.IMPORTANCE_LOW
|
val importance = NotificationManager.IMPORTANCE_LOW
|
||||||
val mChannel = NotificationChannel(AppSettingsService.syncChannelId, name, importance)
|
val mChannel = NotificationChannel(AppSettingsService.SYNC_CHANNEL_ID, name, importance)
|
||||||
|
|
||||||
val newItemsChannelname = getString(R.string.new_items_channel_sync)
|
val newItemsChannelname = getString(R.string.new_items_channel_sync)
|
||||||
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
|
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
|
||||||
val newItemsChannelmChannel =
|
val newItemsChannelmChannel =
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
AppSettingsService.newItemsChannelId,
|
AppSettingsService.NEW_ITEMS_CHANNEL,
|
||||||
newItemsChannelname,
|
newItemsChannelname,
|
||||||
newItemsChannelimportance,
|
newItemsChannelimportance,
|
||||||
)
|
)
|
||||||
|
@ -22,7 +22,9 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
class ReaderActivity : AppCompatActivity(), DIAware {
|
class ReaderActivity :
|
||||||
|
AppCompatActivity(),
|
||||||
|
DIAware {
|
||||||
private var currentItem: Int = 0
|
private var currentItem: Int = 0
|
||||||
|
|
||||||
private lateinit var toolbarMenu: Menu
|
private lateinit var toolbarMenu: Menu
|
||||||
@ -51,6 +53,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
showMenuItem(false)
|
showMenuItem(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:SwallowedException")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityReaderBinding.inflate(layoutInflater)
|
binding = ActivityReaderBinding.inflate(layoutInflater)
|
||||||
@ -99,8 +102,9 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
oldInstanceState.clear()
|
oldInstanceState.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) :
|
private inner class ScreenSlidePagerAdapter(
|
||||||
FragmentStateAdapter(fa) {
|
fa: FragmentActivity,
|
||||||
|
) : FragmentStateAdapter(fa) {
|
||||||
override fun getItemCount(): Int = allItems.size
|
override fun getItemCount(): Int = allItems.size
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position])
|
override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position])
|
||||||
@ -109,25 +113,26 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
override fun onKeyDown(
|
override fun onKeyDown(
|
||||||
keyCode: Int,
|
keyCode: Int,
|
||||||
event: KeyEvent?,
|
event: KeyEvent?,
|
||||||
): Boolean {
|
): Boolean =
|
||||||
return when (keyCode) {
|
when (keyCode) {
|
||||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||||
val currentFragment =
|
val currentFragment =
|
||||||
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
||||||
currentFragment.scrollDown()
|
currentFragment.volumeButtonScrollDown()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||||
val currentFragment =
|
val currentFragment =
|
||||||
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
||||||
currentFragment.scrollUp()
|
currentFragment.volumeButtonScrollUp()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
super.onKeyDown(keyCode, event)
|
super.onKeyDown(keyCode, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun alignmentMenu() {
|
private fun alignmentMenu() {
|
||||||
val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT
|
val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT
|
||||||
@ -187,6 +192,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.onBackPressed()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.star -> {
|
R.id.star -> {
|
||||||
if (allItems[binding.pager.currentItem].starred) {
|
if (allItems[binding.pager.currentItem].starred) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
@ -200,10 +206,12 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
afterSave()
|
afterSave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.align_left -> {
|
R.id.align_left -> {
|
||||||
switchAlignmentSetting(AppSettingsService.ALIGN_LEFT)
|
switchAlignmentSetting(AppSettingsService.ALIGN_LEFT)
|
||||||
refreshFragment()
|
refreshFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.align_justify -> {
|
R.id.align_justify -> {
|
||||||
switchAlignmentSetting(AppSettingsService.JUSTIFY)
|
switchAlignmentSetting(AppSettingsService.JUSTIFY)
|
||||||
refreshFragment()
|
refreshFragment()
|
||||||
|
@ -18,7 +18,9 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
class SourcesActivity : AppCompatActivity(), DIAware {
|
class SourcesActivity :
|
||||||
|
AppCompatActivity(),
|
||||||
|
DIAware {
|
||||||
private lateinit var binding: ActivitySourcesBinding
|
private lateinit var binding: ActivitySourcesBinding
|
||||||
|
|
||||||
override val di by closestDI()
|
override val di by closestDI()
|
||||||
@ -68,11 +70,12 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
|||||||
binding.recyclerView.adapter = mAdapter
|
binding.recyclerView.adapter = mAdapter
|
||||||
mAdapter.notifyDataSetChanged()
|
mAdapter.notifyDataSetChanged()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this@SourcesActivity,
|
.makeText(
|
||||||
R.string.cant_get_sources,
|
this@SourcesActivity,
|
||||||
Toast.LENGTH_SHORT,
|
R.string.cant_get_sources,
|
||||||
).show()
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
CountingIdlingResourceSingleton.decrement()
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,9 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
class UpsertSourceActivity :
|
||||||
|
AppCompatActivity(),
|
||||||
|
DIAware {
|
||||||
private var existingSource: SelfossModel.SourceDetail? = null
|
private var existingSource: SelfossModel.SourceDetail? = null
|
||||||
private var mSpoutsValue: String? = null
|
private var mSpoutsValue: String? = null
|
||||||
|
|
||||||
@ -83,6 +85,7 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:SwallowedException")
|
||||||
private fun handleSpoutsSpinner() {
|
private fun handleSpoutsSpinner() {
|
||||||
val spoutsKV = HashMap<String, String>()
|
val spoutsKV = HashMap<String, String>()
|
||||||
binding.spoutsSpinner.onItemSelectedListener =
|
binding.spoutsSpinner.onItemSelectedListener =
|
||||||
@ -105,11 +108,12 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun handleSpoutFailure(networkIssue: Boolean = false) {
|
fun handleSpoutFailure(networkIssue: Boolean = false) {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this@UpsertSourceActivity,
|
.makeText(
|
||||||
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
|
this@UpsertSourceActivity,
|
||||||
Toast.LENGTH_SHORT,
|
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
|
||||||
).show()
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
binding.progress.visibility = View.GONE
|
binding.progress.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +174,7 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
|||||||
sourceDetailsUnavailable -> {
|
sourceDetailsUnavailable -> {
|
||||||
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val successfullyAddedSource =
|
val successfullyAddedSource =
|
||||||
@ -192,11 +197,12 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
|||||||
if (successfullyAddedSource) {
|
if (successfullyAddedSource) {
|
||||||
finish()
|
finish()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this@UpsertSourceActivity,
|
.makeText(
|
||||||
R.string.cant_create_source,
|
this@UpsertSourceActivity,
|
||||||
Toast.LENGTH_SHORT,
|
R.string.cant_create_source,
|
||||||
).show()
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,10 @@ class ItemCardAdapter(
|
|||||||
return ViewHolder(binding)
|
return ViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleClickListeners(holderBinding: CardItemBinding, position: Int) {
|
private fun handleClickListeners(
|
||||||
|
holderBinding: CardItemBinding,
|
||||||
|
position: Int,
|
||||||
|
) {
|
||||||
holderBinding.favButton.setOnClickListener {
|
holderBinding.favButton.setOnClickListener {
|
||||||
val item = items[position]
|
val item = items[position]
|
||||||
if (item.starred) {
|
if (item.starred) {
|
||||||
@ -96,12 +99,13 @@ class ItemCardAdapter(
|
|||||||
|
|
||||||
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
|
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
|
||||||
|
|
||||||
binding.sourceTitleAndDate.text = try {
|
binding.sourceTitleAndDate.text =
|
||||||
itm.sourceAuthorAndDate()
|
try {
|
||||||
} catch (e: Exception) {
|
itm.sourceAuthorAndDate()
|
||||||
e.sendSilentlyWithAcraWithName("ItemCardAdapter parse date")
|
} catch (e: Exception) {
|
||||||
itm.sourceAuthorOnly()
|
e.sendSilentlyWithAcraWithName("ItemCardAdapter parse date")
|
||||||
}
|
itm.sourceAuthorOnly()
|
||||||
|
}
|
||||||
|
|
||||||
if (!appSettingsService.isFullHeightCardsEnabled()) {
|
if (!appSettingsService.isFullHeightCardsEnabled()) {
|
||||||
binding.itemImage.maxHeight = imageMaxHeight
|
binding.itemImage.maxHeight = imageMaxHeight
|
||||||
@ -125,5 +129,7 @@ class ItemCardAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class ViewHolder(
|
||||||
|
val binding: CardItemBinding,
|
||||||
|
) : RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
@ -53,12 +53,13 @@ class ItemListAdapter(
|
|||||||
|
|
||||||
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
|
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
|
||||||
|
|
||||||
binding.sourceTitleAndDate.text = try {
|
binding.sourceTitleAndDate.text =
|
||||||
itm.sourceAuthorAndDate()
|
try {
|
||||||
} catch (e: Exception) {
|
itm.sourceAuthorAndDate()
|
||||||
e.sendSilentlyWithAcraWithName("ItemListAdapter parse date")
|
} catch (e: Exception) {
|
||||||
itm.sourceAuthorOnly()
|
e.sendSilentlyWithAcraWithName("ItemListAdapter parse date")
|
||||||
}
|
itm.sourceAuthorOnly()
|
||||||
|
}
|
||||||
|
|
||||||
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
|
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
|
||||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||||
@ -72,5 +73,7 @@ class ItemListAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root)
|
inner class ViewHolder(
|
||||||
|
val binding: ListItemBinding,
|
||||||
|
) : RecyclerView.ViewHolder(binding.root)
|
||||||
}
|
}
|
@ -18,7 +18,9 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.kodein.di.DIAware
|
import org.kodein.di.DIAware
|
||||||
|
|
||||||
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>(), DIAware {
|
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> :
|
||||||
|
RecyclerView.Adapter<VH>(),
|
||||||
|
DIAware {
|
||||||
abstract val items: ArrayList<SelfossModel.Item>
|
abstract val items: ArrayList<SelfossModel.Item>
|
||||||
abstract val repository: Repository
|
abstract val repository: Repository
|
||||||
abstract val binding: ViewBinding
|
abstract val binding: ViewBinding
|
||||||
@ -45,8 +47,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
|||||||
app.findViewById(R.id.coordLayout),
|
app.findViewById(R.id.coordLayout),
|
||||||
R.string.marked_as_read,
|
R.string.marked_as_read,
|
||||||
Snackbar.LENGTH_LONG,
|
Snackbar.LENGTH_LONG,
|
||||||
)
|
).setAction(R.string.undo_string) {
|
||||||
.setAction(R.string.undo_string) {
|
|
||||||
unreadItemAtIndex(item, position, false)
|
unreadItemAtIndex(item, position, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,8 +67,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
|||||||
app.findViewById(R.id.coordLayout),
|
app.findViewById(R.id.coordLayout),
|
||||||
R.string.marked_as_unread,
|
R.string.marked_as_unread,
|
||||||
Snackbar.LENGTH_LONG,
|
Snackbar.LENGTH_LONG,
|
||||||
)
|
).setAction(R.string.undo_string) {
|
||||||
.setAction(R.string.undo_string) {
|
|
||||||
readItemAtIndex(item, position, false)
|
readItemAtIndex(item, position, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +77,10 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
|||||||
s.show()
|
s.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun handleLinkOpening(holderBinding: ViewBinding, position: Int) {
|
protected fun handleLinkOpening(
|
||||||
|
holderBinding: ViewBinding,
|
||||||
|
position: Int,
|
||||||
|
) {
|
||||||
holderBinding.root.setOnClickListener {
|
holderBinding.root.setOnClickListener {
|
||||||
repository.setReaderItems(items)
|
repository.setReaderItems(items)
|
||||||
c.openItemUrl(
|
c.openItemUrl(
|
||||||
|
@ -29,7 +29,8 @@ import org.kodein.di.instance
|
|||||||
class SourcesListAdapter(
|
class SourcesListAdapter(
|
||||||
private val app: Activity,
|
private val app: Activity,
|
||||||
private val items: ArrayList<SelfossModel.SourceDetail>,
|
private val items: ArrayList<SelfossModel.SourceDetail>,
|
||||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
|
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(),
|
||||||
|
DIAware {
|
||||||
private val c: Context = app.baseContext
|
private val c: Context = app.baseContext
|
||||||
private lateinit var binding: SourceListItemBinding
|
private lateinit var binding: SourceListItemBinding
|
||||||
|
|
||||||
@ -61,11 +62,12 @@ class SourcesListAdapter(
|
|||||||
notifyItemRemoved(position)
|
notifyItemRemoved(position)
|
||||||
notifyItemRangeChanged(position, itemCount)
|
notifyItemRangeChanged(position, itemCount)
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast
|
||||||
app,
|
.makeText(
|
||||||
R.string.can_delete_source,
|
app,
|
||||||
Toast.LENGTH_SHORT,
|
R.string.can_delete_source,
|
||||||
).show()
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,5 +101,7 @@ class SourcesListAdapter(
|
|||||||
|
|
||||||
override fun getItemCount(): Int = items.size
|
override fun getItemCount(): Int = items.size
|
||||||
|
|
||||||
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView)
|
inner class ViewHolder(
|
||||||
|
val mView: ConstraintLayout,
|
||||||
|
) : RecyclerView.ViewHolder(mView)
|
||||||
}
|
}
|
||||||
|
@ -23,11 +23,15 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.kodein.di.DIAware
|
import org.kodein.di.DIAware
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import java.util.*
|
import java.util.Timer
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
class LoadingWorker(val context: Context, params: WorkerParameters) :
|
private const val NOTIFICATION_DELAY = 4000L
|
||||||
Worker(context, params),
|
|
||||||
|
class LoadingWorker(
|
||||||
|
val context: Context,
|
||||||
|
params: WorkerParameters,
|
||||||
|
) : Worker(context, params),
|
||||||
DIAware {
|
DIAware {
|
||||||
override val di by lazy { (applicationContext as MyApp).di }
|
override val di by lazy { (applicationContext as MyApp).di }
|
||||||
private val repository: Repository by instance()
|
private val repository: Repository by instance()
|
||||||
@ -40,12 +44,13 @@ class LoadingWorker(val context: Context, params: WorkerParameters) :
|
|||||||
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
val notification =
|
val notification =
|
||||||
NotificationCompat.Builder(applicationContext, AppSettingsService.syncChannelId)
|
NotificationCompat
|
||||||
|
.Builder(applicationContext, AppSettingsService.SYNC_CHANNEL_ID)
|
||||||
.setContentTitle(context.getString(R.string.loading_notification_title))
|
.setContentTitle(context.getString(R.string.loading_notification_title))
|
||||||
.setContentText(context.getString(R.string.loading_notification_text))
|
.setContentText(context.getString(R.string.loading_notification_text))
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setPriority(PRIORITY_LOW)
|
.setPriority(PRIORITY_LOW)
|
||||||
.setChannelId(AppSettingsService.syncChannelId)
|
.setChannelId(AppSettingsService.SYNC_CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
|
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
|
||||||
|
|
||||||
notificationManager.notify(1, notification.build())
|
notificationManager.notify(1, notification.build())
|
||||||
@ -87,28 +92,27 @@ class LoadingWorker(val context: Context, params: WorkerParameters) :
|
|||||||
PendingIntent.getActivity(context, 0, intent, pflags)
|
PendingIntent.getActivity(context, 0, intent, pflags)
|
||||||
|
|
||||||
val newItemsNotification =
|
val newItemsNotification =
|
||||||
NotificationCompat.Builder(
|
NotificationCompat
|
||||||
applicationContext,
|
.Builder(
|
||||||
AppSettingsService.newItemsChannelId,
|
applicationContext,
|
||||||
)
|
AppSettingsService.NEW_ITEMS_CHANNEL,
|
||||||
.setContentTitle(context.getString(R.string.new_items_notification_title))
|
).setContentTitle(context.getString(R.string.new_items_notification_title))
|
||||||
.setContentText(
|
.setContentText(
|
||||||
context.getString(
|
context.getString(
|
||||||
R.string.new_items_notification_text,
|
R.string.new_items_notification_text,
|
||||||
newSize,
|
newSize,
|
||||||
),
|
),
|
||||||
)
|
).setPriority(PRIORITY_DEFAULT)
|
||||||
.setPriority(PRIORITY_DEFAULT)
|
.setChannelId(AppSettingsService.NEW_ITEMS_CHANNEL)
|
||||||
.setChannelId(AppSettingsService.newItemsChannelId)
|
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
||||||
|
|
||||||
Timer("", false).schedule(4000) {
|
Timer("", false).schedule(NOTIFICATION_DELAY) {
|
||||||
notificationManager.notify(2, newItemsNotification.build())
|
notificationManager.notify(2, newItemsNotification.build())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timer("", false).schedule(4000) {
|
Timer("", false).schedule(NOTIFICATION_DELAY) {
|
||||||
notificationManager.cancel(1)
|
notificationManager.cancel(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.fragments
|
package bou.amine.apps.readerforselfossv2.android.fragments
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.TypedArray
|
import android.content.res.TypedArray
|
||||||
@ -8,6 +9,7 @@ import android.graphics.Typeface
|
|||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
|
import android.util.TypedValue.DATA_NULL_UNDEFINED
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.InflateException
|
import android.view.InflateException
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -64,8 +66,14 @@ import java.util.concurrent.ExecutionException
|
|||||||
|
|
||||||
private const val IMAGE_JPG = "image/jpg"
|
private const val IMAGE_JPG = "image/jpg"
|
||||||
|
|
||||||
class ArticleFragment : Fragment(), DIAware {
|
private const val WHITE_COLOR_HEX = 0xFFFFFF
|
||||||
private var fontSize: Int = 16
|
|
||||||
|
private const val DEFAULT_FONT_SIZE = 16
|
||||||
|
|
||||||
|
class ArticleFragment :
|
||||||
|
Fragment(),
|
||||||
|
DIAware {
|
||||||
|
private var fontSize: Int = DEFAULT_FONT_SIZE
|
||||||
private lateinit var item: SelfossModel.Item
|
private lateinit var item: SelfossModel.Item
|
||||||
private lateinit var url: String
|
private lateinit var url: String
|
||||||
private lateinit var contentText: String
|
private lateinit var contentText: String
|
||||||
@ -96,6 +104,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
item = pi.toModel()
|
item = pi.toModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:LongMethod")
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
@ -113,12 +122,13 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
contentText = item.content
|
contentText = item.content
|
||||||
contentTitle = item.title.getHtmlDecoded()
|
contentTitle = item.title.getHtmlDecoded()
|
||||||
contentImage = item.getThumbnail(repository.baseUrl)
|
contentImage = item.getThumbnail(repository.baseUrl)
|
||||||
contentSource = try {
|
contentSource =
|
||||||
item.sourceAuthorAndDate()
|
try {
|
||||||
} catch (e: Exception) {
|
item.sourceAuthorAndDate()
|
||||||
e.sendSilentlyWithAcraWithName("Article Fragment parse date")
|
} catch (e: Exception) {
|
||||||
item.sourceAuthorOnly()
|
e.sendSilentlyWithAcraWithName("Article Fragment parse date")
|
||||||
}
|
item.sourceAuthorOnly()
|
||||||
|
}
|
||||||
allImages = item.getImages()
|
allImages = item.getImages()
|
||||||
|
|
||||||
fontSize = appSettingsService.getFontSize()
|
fontSize = appSettingsService.getFontSize()
|
||||||
@ -164,7 +174,8 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
} catch (e: InflateException) {
|
} catch (e: InflateException) {
|
||||||
e.sendSilentlyWithAcraWithName("webview not available")
|
e.sendSilentlyWithAcraWithName("webview not available")
|
||||||
try {
|
try {
|
||||||
AlertDialog.Builder(requireContext())
|
AlertDialog
|
||||||
|
.Builder(requireContext())
|
||||||
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
||||||
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
||||||
.setPositiveButton(
|
.setPositiveButton(
|
||||||
@ -172,8 +183,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
) { _, _ ->
|
) { _, _ ->
|
||||||
appSettingsService.disableArticleViewer()
|
appSettingsService.disableArticleViewer()
|
||||||
requireActivity().finish()
|
requireActivity().finish()
|
||||||
}
|
}.create()
|
||||||
.create()
|
|
||||||
.show()
|
.show()
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
e.sendSilentlyWithAcraWithName("Context required is null")
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
@ -232,21 +242,23 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
repository.markAsRead(this@ArticleFragment.item)
|
repository.markAsRead(this@ArticleFragment.item)
|
||||||
}
|
}
|
||||||
this@ArticleFragment.item.unread = false
|
this@ArticleFragment.item.unread = false
|
||||||
Toast.makeText(
|
Toast
|
||||||
requireContext(),
|
.makeText(
|
||||||
R.string.marked_as_read,
|
requireContext(),
|
||||||
Toast.LENGTH_LONG,
|
R.string.marked_as_read,
|
||||||
).show()
|
Toast.LENGTH_LONG,
|
||||||
|
).show()
|
||||||
} else {
|
} else {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.unmarkAsRead(this@ArticleFragment.item)
|
repository.unmarkAsRead(this@ArticleFragment.item)
|
||||||
}
|
}
|
||||||
this@ArticleFragment.item.unread = true
|
this@ArticleFragment.item.unread = true
|
||||||
Toast.makeText(
|
Toast
|
||||||
context,
|
.makeText(
|
||||||
R.string.marked_as_unread,
|
context,
|
||||||
Toast.LENGTH_LONG,
|
R.string.marked_as_unread,
|
||||||
).show()
|
Toast.LENGTH_LONG,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
e.sendSilentlyWithAcraWithName("Context required is null")
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
@ -273,6 +285,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:SwallowedException")
|
||||||
private fun getContentFromMercury() {
|
private fun getContentFromMercury() {
|
||||||
binding.progressBar.visibility = View.VISIBLE
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
|
||||||
@ -311,16 +324,15 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLeadImage(lead_image_url: String?) {
|
private fun handleLeadImage(leadImageUrl: String?) {
|
||||||
if (!lead_image_url.isNullOrEmpty() && context != null) {
|
if (!leadImageUrl.isNullOrEmpty() && context != null) {
|
||||||
binding.imageView.visibility = View.VISIBLE
|
binding.imageView.visibility = View.VISIBLE
|
||||||
Glide
|
Glide
|
||||||
.with(requireContext())
|
.with(requireContext())
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(
|
.load(
|
||||||
lead_image_url,
|
leadImageUrl,
|
||||||
)
|
).apply(RequestOptions.fitCenterTransform())
|
||||||
.apply(RequestOptions.fitCenterTransform())
|
|
||||||
.into(binding.imageView)
|
.into(binding.imageView)
|
||||||
} else {
|
} else {
|
||||||
binding.imageView.visibility = View.GONE
|
binding.imageView.visibility = View.GONE
|
||||||
@ -334,84 +346,133 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
override fun shouldOverrideUrlLoading(
|
override fun shouldOverrideUrlLoading(
|
||||||
view: WebView?,
|
view: WebView?,
|
||||||
url: String,
|
url: String,
|
||||||
): Boolean {
|
): Boolean =
|
||||||
return if (context != null && url.isUrlValid() && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
if (context != null &&
|
||||||
|
url.isUrlValid() &&
|
||||||
|
binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
||||||
|
) {
|
||||||
requireContext().openUrlInBrowser(url)
|
requireContext().openUrlInBrowser(url)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
@Suppress("detekt:LongMethod", "detekt:SwallowedException")
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
override fun shouldInterceptRequest(
|
override fun shouldInterceptRequest(
|
||||||
view: WebView,
|
view: WebView,
|
||||||
url: String,
|
url: String,
|
||||||
): WebResourceResponse? {
|
): WebResourceResponse? {
|
||||||
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||||
|
var glideResource: WebResourceResponse? = null
|
||||||
if (url.lowercase(Locale.US).contains(".jpg") ||
|
if (url.lowercase(Locale.US).contains(".jpg") ||
|
||||||
url.lowercase(Locale.US)
|
url
|
||||||
|
.lowercase(Locale.US)
|
||||||
.contains(".jpeg")
|
.contains(".jpeg")
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val image =
|
val image =
|
||||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
Glide
|
||||||
|
.with(view)
|
||||||
|
.asBitmap()
|
||||||
|
.apply(glideOptions)
|
||||||
|
.load(url)
|
||||||
|
.submit()
|
||||||
.get()
|
.get()
|
||||||
return WebResourceResponse(
|
glideResource =
|
||||||
IMAGE_JPG,
|
WebResourceResponse(
|
||||||
"UTF-8",
|
IMAGE_JPG,
|
||||||
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG),
|
"UTF-8",
|
||||||
)
|
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG),
|
||||||
|
)
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
} else if (url.lowercase(Locale.US).contains(".png")) {
|
} else if (url.lowercase(Locale.US).contains(".png")) {
|
||||||
try {
|
try {
|
||||||
val image =
|
val image =
|
||||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
Glide
|
||||||
|
.with(view)
|
||||||
|
.asBitmap()
|
||||||
|
.apply(glideOptions)
|
||||||
|
.load(url)
|
||||||
|
.submit()
|
||||||
.get()
|
.get()
|
||||||
return WebResourceResponse(
|
glideResource =
|
||||||
IMAGE_JPG,
|
WebResourceResponse(
|
||||||
"UTF-8",
|
IMAGE_JPG,
|
||||||
getBitmapInputStream(image, Bitmap.CompressFormat.PNG),
|
"UTF-8",
|
||||||
)
|
getBitmapInputStream(image, Bitmap.CompressFormat.PNG),
|
||||||
|
)
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
||||||
try {
|
try {
|
||||||
val image =
|
val image =
|
||||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
Glide
|
||||||
|
.with(view)
|
||||||
|
.asBitmap()
|
||||||
|
.apply(glideOptions)
|
||||||
|
.load(url)
|
||||||
|
.submit()
|
||||||
.get()
|
.get()
|
||||||
return WebResourceResponse(
|
glideResource =
|
||||||
IMAGE_JPG,
|
WebResourceResponse(
|
||||||
"UTF-8",
|
IMAGE_JPG,
|
||||||
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP),
|
"UTF-8",
|
||||||
)
|
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP),
|
||||||
|
)
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.shouldInterceptRequest(view, url)
|
return glideResource ?: super.shouldInterceptRequest(view, url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:LongMethod", "detekt:ImplicitDefaultLocale")
|
||||||
private fun htmlToWebview() {
|
private fun htmlToWebview() {
|
||||||
|
val context: Context
|
||||||
|
try {
|
||||||
|
context = requireContext()
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val colorOnSurface = TypedValue()
|
||||||
|
val colorSurface = TypedValue()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
|
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
|
||||||
val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
|
val a: TypedArray = context.obtainStyledAttributes(resId, attrs)
|
||||||
|
|
||||||
binding.webcontent.settings.standardFontFamily = a.getString(0)
|
binding.webcontent.settings.standardFontFamily = a.getString(0)
|
||||||
binding.webcontent.visibility = View.VISIBLE
|
binding.webcontent.visibility = View.VISIBLE
|
||||||
|
|
||||||
val colorOnSurface = TypedValue()
|
context.theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
|
||||||
requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
|
|
||||||
|
|
||||||
val colorSurface = TypedValue()
|
context.theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
|
||||||
requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context issue when setting attributes, but context wasn't null before")
|
||||||
|
}
|
||||||
|
|
||||||
|
val colorSurfaceString =
|
||||||
|
String.format(
|
||||||
|
"#%06X",
|
||||||
|
WHITE_COLOR_HEX and (if (colorSurface.data != DATA_NULL_UNDEFINED) colorSurface.data else WHITE_COLOR_HEX),
|
||||||
|
)
|
||||||
|
|
||||||
|
val colorOnSurfaceString =
|
||||||
|
String.format(
|
||||||
|
"#%06X",
|
||||||
|
WHITE_COLOR_HEX and (if (colorOnSurface.data != DATA_NULL_UNDEFINED) colorOnSurface.data else 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
binding.webcontent.settings.useWideViewPort = true
|
binding.webcontent.settings.useWideViewPort = true
|
||||||
binding.webcontent.settings.loadWithOverviewMode = true
|
binding.webcontent.settings.loadWithOverviewMode = true
|
||||||
binding.webcontent.settings.javaScriptEnabled = false
|
binding.webcontent.settings.javaScriptEnabled = false
|
||||||
@ -422,19 +483,25 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
activity,
|
activity,
|
||||||
object : GestureDetector.SimpleOnGestureListener() {
|
object : GestureDetector.SimpleOnGestureListener() {
|
||||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
override fun onSingleTapUp(e: MotionEvent): Boolean = performClick()
|
||||||
return performClick()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
|
binding.webcontent.setOnTouchListener { _, event ->
|
||||||
|
gestureDetector.onTouchEvent(
|
||||||
|
event,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
binding.webcontent.settings.layoutAlgorithm =
|
binding.webcontent.settings.layoutAlgorithm =
|
||||||
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context is null but wasn't, and that's causing issues with webview config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
var baseUrl: String? = null
|
var baseUrl: String? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val itemUrl = URL(url)
|
val itemUrl = URL(url)
|
||||||
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
||||||
@ -484,12 +551,12 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
| color: ${
|
| color: ${
|
||||||
String.format(
|
String.format(
|
||||||
"#%06X",
|
"#%06X",
|
||||||
0xFFFFFF and resources.getColor(R.color.colorAccent),
|
WHITE_COLOR_HEX and context.resources.getColor(R.color.colorAccent),
|
||||||
)
|
)
|
||||||
} !important;
|
} !important;
|
||||||
| }
|
| }
|
||||||
| *:not(a) {
|
| *:not(a) {
|
||||||
| color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
|
| color: $colorOnSurfaceString;
|
||||||
| }
|
| }
|
||||||
| * {
|
| * {
|
||||||
| font-size: ${fontSize}px;
|
| font-size: ${fontSize}px;
|
||||||
@ -497,26 +564,11 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
| word-break: break-word;
|
| word-break: break-word;
|
||||||
| overflow:hidden;
|
| overflow:hidden;
|
||||||
| line-height: 1.5em;
|
| line-height: 1.5em;
|
||||||
| background-color: ${
|
| background-color: $colorSurfaceString;
|
||||||
String.format(
|
|
||||||
"#%06X",
|
|
||||||
0xFFFFFF and colorSurface.data,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
| }
|
| }
|
||||||
| body, html {
|
| body, html {
|
||||||
| background-color: ${
|
| background-color: $colorSurfaceString !important;
|
||||||
String.format(
|
| border-color: $colorSurfaceString !important;
|
||||||
"#%06X",
|
|
||||||
0xFFFFFF and colorSurface.data,
|
|
||||||
)
|
|
||||||
} !important;
|
|
||||||
| border-color: ${
|
|
||||||
String.format(
|
|
||||||
"#%06X",
|
|
||||||
0xFFFFFF and colorSurface.data,
|
|
||||||
)
|
|
||||||
} !important;
|
|
||||||
| padding: 0 !important;
|
| padding: 0 !important;
|
||||||
| margin: 0 !important;
|
| margin: 0 !important;
|
||||||
| }
|
| }
|
||||||
@ -526,12 +578,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
| pre, code {
|
| pre, code {
|
||||||
| white-space: pre-wrap;
|
| white-space: pre-wrap;
|
||||||
| width:100%;
|
| width:100%;
|
||||||
| background-color: ${
|
| background-color: $colorSurfaceString;
|
||||||
String.format(
|
|
||||||
"#%06X",
|
|
||||||
0xFFFFFF and colorSurface.data,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
| }
|
| }
|
||||||
| </style>
|
| </style>
|
||||||
| $fontLinkAndStyle
|
| $fontLinkAndStyle
|
||||||
@ -545,16 +592,16 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
e.sendSilentlyWithAcraWithName("Context required is null")
|
e.sendSilentlyWithAcraWithName("Context required is still null ?")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scrollDown() {
|
fun volumeButtonScrollDown() {
|
||||||
val height = binding.nestedScrollView.measuredHeight
|
val height = binding.nestedScrollView.measuredHeight
|
||||||
binding.nestedScrollView.smoothScrollBy(0, height / 2)
|
binding.nestedScrollView.smoothScrollBy(0, height / 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scrollUp() {
|
fun volumeButtonScrollUp() {
|
||||||
val height = binding.nestedScrollView.measuredHeight
|
val height = binding.nestedScrollView.measuredHeight
|
||||||
binding.nestedScrollView.smoothScrollBy(0, -height / 2)
|
binding.nestedScrollView.smoothScrollBy(0, -height / 2)
|
||||||
}
|
}
|
||||||
@ -581,10 +628,11 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun performClick(): Boolean {
|
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
|
binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
|
||||||
)
|
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
|
val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
|
||||||
|
|
||||||
|
@ -33,7 +33,11 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.x.closestDI
|
import org.kodein.di.android.x.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
private const val DRAWABLE_SIZE = 30
|
||||||
|
|
||||||
|
class FilterSheetFragment :
|
||||||
|
BottomSheetDialogFragment(),
|
||||||
|
DIAware {
|
||||||
private lateinit var binding: FilterFragmentBinding
|
private lateinit var binding: FilterFragmentBinding
|
||||||
override val di: DI by closestDI()
|
override val di: DI by closestDI()
|
||||||
private val repository: Repository by instance()
|
private val repository: Repository by instance()
|
||||||
@ -80,7 +84,8 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
|||||||
val c = Chip(context)
|
val c = Chip(context)
|
||||||
c.ellipsize = TextUtils.TruncateAt.END
|
c.ellipsize = TextUtils.TruncateAt.END
|
||||||
|
|
||||||
Glide.with(context)
|
Glide
|
||||||
|
.with(context)
|
||||||
.load(source.getIcon(repository.baseUrl))
|
.load(source.getIcon(repository.baseUrl))
|
||||||
.into(
|
.into(
|
||||||
object : ViewTarget<Chip?, Drawable?>(c) {
|
object : ViewTarget<Chip?, Drawable?>(c) {
|
||||||
@ -153,8 +158,8 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
|||||||
}
|
}
|
||||||
gd.setColor(gdColor)
|
gd.setColor(gdColor)
|
||||||
gd.shape = GradientDrawable.RECTANGLE
|
gd.shape = GradientDrawable.RECTANGLE
|
||||||
gd.setSize(30, 30)
|
gd.setSize(DRAWABLE_SIZE, DRAWABLE_SIZE)
|
||||||
gd.cornerRadius = 30F
|
gd.cornerRadius = DRAWABLE_SIZE.toFloat()
|
||||||
c.chipIcon = gd
|
c.chipIcon = gd
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
|
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
|
||||||
|
@ -14,7 +14,7 @@ class ImageFragment : Fragment() {
|
|||||||
private lateinit var imageUrl: String
|
private lateinit var imageUrl: String
|
||||||
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||||
private var _binding: FragmentImageBinding? = null
|
private var _binding: FragmentImageBinding? = null
|
||||||
private val binding get() = _binding
|
val binding get() = _binding
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -31,7 +31,8 @@ class ImageFragment : Fragment() {
|
|||||||
val view = binding?.root
|
val view = binding?.root
|
||||||
|
|
||||||
binding!!.photoView.visibility = View.VISIBLE
|
binding!!.photoView.visibility = View.VISIBLE
|
||||||
Glide.with(requireActivity())
|
Glide
|
||||||
|
.with(requireActivity())
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.apply(glideOptions)
|
.apply(glideOptions)
|
||||||
.load(imageUrl)
|
.load(imageUrl)
|
||||||
|
@ -9,17 +9,22 @@ import com.bumptech.glide.Glide
|
|||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
|
||||||
|
private const val PRELOAD_IMAGE_TIMEOUT = 10000
|
||||||
|
|
||||||
fun SelfossModel.Item.preloadImages(context: Context): Boolean {
|
fun SelfossModel.Item.preloadImages(context: Context): Boolean {
|
||||||
val imageUrls = this.getImages()
|
val imageUrls = this.getImages()
|
||||||
|
|
||||||
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000)
|
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(PRELOAD_IMAGE_TIMEOUT)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (url in imageUrls) {
|
for (url in imageUrls) {
|
||||||
if (URLUtil.isValidUrl(url)) {
|
if (URLUtil.isValidUrl(url)) {
|
||||||
Glide.with(context).asBitmap()
|
Glide
|
||||||
|
.with(context)
|
||||||
|
.asBitmap()
|
||||||
.apply(glideOptions)
|
.apply(glideOptions)
|
||||||
.load(url).submit()
|
.load(url)
|
||||||
|
.submit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
|
@ -17,12 +17,19 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBin
|
|||||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
|
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService.Companion.API_ITEMS_NUMBER
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService.Companion.CURRENT_THEME
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService.Companion.READER_FONT_SIZE
|
||||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||||
import org.kodein.di.DIAware
|
import org.kodein.di.DIAware
|
||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
|
|
||||||
private const val TITLE_TAG = "settingsActivityTitle"
|
private const val TITLE_TAG = "settingsActivityTitle"
|
||||||
|
|
||||||
|
const val MAX_ITEMS_NUMBER = 200
|
||||||
|
|
||||||
|
private const val MIN_ITEMS_NUMBER = 1
|
||||||
|
|
||||||
class SettingsActivity :
|
class SettingsActivity :
|
||||||
AppCompatActivity(),
|
AppCompatActivity(),
|
||||||
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
|
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
|
||||||
@ -61,15 +68,14 @@ class SettingsActivity :
|
|||||||
outState.putCharSequence(TITLE_TAG, title)
|
outState.putCharSequence(TITLE_TAG, title)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSupportNavigateUp(): Boolean {
|
override fun onSupportNavigateUp(): Boolean =
|
||||||
return if (supportFragmentManager.popBackStackImmediate()) {
|
if (supportFragmentManager.popBackStackImmediate()) {
|
||||||
supportActionBar?.title = getText(R.string.title_activity_settings)
|
supportActionBar?.title = getText(R.string.title_activity_settings)
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPreferenceStartFragment(
|
override fun onPreferenceStartFragment(
|
||||||
caller: PreferenceFragmentCompat,
|
caller: PreferenceFragmentCompat,
|
||||||
@ -78,15 +84,17 @@ class SettingsActivity :
|
|||||||
// Instantiate the new Fragment
|
// Instantiate the new Fragment
|
||||||
val args = pref.extras
|
val args = pref.extras
|
||||||
val fragment =
|
val fragment =
|
||||||
supportFragmentManager.fragmentFactory.instantiate(
|
supportFragmentManager.fragmentFactory
|
||||||
classLoader,
|
.instantiate(
|
||||||
pref.fragment.toString(),
|
classLoader,
|
||||||
).apply {
|
pref.fragment.toString(),
|
||||||
arguments = args
|
).apply {
|
||||||
setTargetFragment(caller, 0)
|
arguments = args
|
||||||
}
|
setTargetFragment(caller, 0)
|
||||||
|
}
|
||||||
// Replace the existing Fragment with the new Fragment
|
// Replace the existing Fragment with the new Fragment
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
.replace(R.id.settings, fragment)
|
.replace(R.id.settings, fragment)
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commit()
|
.commit()
|
||||||
@ -102,10 +110,10 @@ class SettingsActivity :
|
|||||||
) {
|
) {
|
||||||
setPreferencesFromResource(R.xml.pref_main, rootKey)
|
setPreferencesFromResource(R.xml.pref_main, rootKey)
|
||||||
|
|
||||||
preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener =
|
preferenceManager.findPreference<Preference>(CURRENT_THEME)?.onPreferenceChangeListener =
|
||||||
Preference.OnPreferenceChangeListener { _, newValue ->
|
Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
AppCompatDelegate.setDefaultNightMode(
|
AppCompatDelegate.setDefaultNightMode(
|
||||||
newValue.toString().toInt()
|
newValue.toString().toInt(),
|
||||||
) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
|
) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -131,7 +139,7 @@ class SettingsActivity :
|
|||||||
setPreferencesFromResource(R.xml.pref_general, rootKey)
|
setPreferencesFromResource(R.xml.pref_general, rootKey)
|
||||||
|
|
||||||
val editTextPreference =
|
val editTextPreference =
|
||||||
preferenceManager.findPreference<EditTextPreference>("prefer_api_items_number")
|
preferenceManager.findPreference<EditTextPreference>(API_ITEMS_NUMBER)
|
||||||
editTextPreference?.setOnBindEditTextListener { editText ->
|
editTextPreference?.setOnBindEditTextListener { editText ->
|
||||||
editText.inputType = InputType.TYPE_CLASS_NUMBER
|
editText.inputType = InputType.TYPE_CLASS_NUMBER
|
||||||
editText.filters =
|
editText.filters =
|
||||||
@ -139,13 +147,14 @@ class SettingsActivity :
|
|||||||
InputFilter { source, _, _, dest, _, _ ->
|
InputFilter { source, _, _, dest, _, _ ->
|
||||||
try {
|
try {
|
||||||
val input: Int = (dest.toString() + source.toString()).toInt()
|
val input: Int = (dest.toString() + source.toString()).toInt()
|
||||||
if (input in 1..200) return@InputFilter null
|
if (input in MIN_ITEMS_NUMBER..MAX_ITEMS_NUMBER) return@InputFilter null
|
||||||
} catch (nfe: NumberFormatException) {
|
} catch (nfe: NumberFormatException) {
|
||||||
Toast.makeText(
|
Toast
|
||||||
activity,
|
.makeText(
|
||||||
R.string.items_number_should_be_number,
|
activity,
|
||||||
Toast.LENGTH_LONG
|
R.string.items_number_should_be_number,
|
||||||
).show()
|
Toast.LENGTH_LONG,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
""
|
""
|
||||||
},
|
},
|
||||||
@ -161,7 +170,7 @@ class SettingsActivity :
|
|||||||
) {
|
) {
|
||||||
setPreferencesFromResource(R.xml.pref_viewer, rootKey)
|
setPreferencesFromResource(R.xml.pref_viewer, rootKey)
|
||||||
|
|
||||||
val fontSize = preferenceManager.findPreference<EditTextPreference>("reader_font_size")
|
val fontSize = preferenceManager.findPreference<EditTextPreference>(READER_FONT_SIZE)
|
||||||
fontSize?.setOnBindEditTextListener { editText ->
|
fontSize?.setOnBindEditTextListener { editText ->
|
||||||
editText.inputType = InputType.TYPE_CLASS_NUMBER
|
editText.inputType = InputType.TYPE_CLASS_NUMBER
|
||||||
editText.addTextChangedListener {
|
editText.addTextChangedListener {
|
||||||
@ -218,23 +227,6 @@ class SettingsActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThemePreferenceFragment : PreferenceFragmentCompat() {
|
|
||||||
override fun onCreatePreferences(
|
|
||||||
savedInstanceState: Bundle?,
|
|
||||||
rootKey: String?,
|
|
||||||
) {
|
|
||||||
setPreferencesFromResource(R.xml.pref_theme, rootKey)
|
|
||||||
|
|
||||||
preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _, newValue ->
|
|
||||||
AppCompatDelegate.setDefaultNightMode(
|
|
||||||
newValue.toString().toInt()
|
|
||||||
) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LinksPreferenceFragment : PreferenceFragmentCompat() {
|
class LinksPreferenceFragment : PreferenceFragmentCompat() {
|
||||||
private fun openUrl(url: String) {
|
private fun openUrl(url: String) {
|
||||||
context?.openUrlInBrowser(url)
|
context?.openUrlInBrowser(url)
|
||||||
@ -248,19 +240,19 @@ class SettingsActivity :
|
|||||||
|
|
||||||
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener =
|
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
openUrl(AppSettingsService.trackerUrl)
|
openUrl(AppSettingsService.BUG_URL)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
preferenceManager.findPreference<Preference>("sourceLink")?.onPreferenceClickListener =
|
preferenceManager.findPreference<Preference>("sourceLink")?.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
openUrl(AppSettingsService.sourceUrl)
|
openUrl(AppSettingsService.SOURCE_URL)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
preferenceManager.findPreference<Preference>("translation")?.onPreferenceClickListener =
|
preferenceManager.findPreference<Preference>("translation")?.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
openUrl(AppSettingsService.translationUrl)
|
openUrl(AppSettingsService.TRANSLATION_URL)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package bou.amine.apps.readerforselfossv2.android.testing
|
|||||||
import androidx.test.espresso.idling.CountingIdlingResource
|
import androidx.test.espresso.idling.CountingIdlingResource
|
||||||
|
|
||||||
object CountingIdlingResourceSingleton {
|
object CountingIdlingResourceSingleton {
|
||||||
|
|
||||||
private const val RESOURCE = "GLOBAL"
|
private const val RESOURCE = "GLOBAL"
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
|
@ -2,7 +2,6 @@ package bou.amine.apps.readerforselfossv2.android.testing
|
|||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
|
||||||
|
|
||||||
class TestingHelper {
|
class TestingHelper {
|
||||||
fun isUnitTest(): Boolean {
|
fun isUnitTest(): Boolean {
|
||||||
var device = Build.DEVICE
|
var device = Build.DEVICE
|
||||||
|
@ -16,9 +16,10 @@ fun Context.shareLink(
|
|||||||
sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle)
|
sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle)
|
||||||
sendIntent.type = "text/plain"
|
sendIntent.type = "text/plain"
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent.createChooser(
|
Intent
|
||||||
sendIntent,
|
.createChooser(
|
||||||
getString(R.string.share),
|
sendIntent,
|
||||||
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
getString(R.string.share),
|
||||||
|
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,5 @@ class CircleImageView
|
|||||||
textView.text = text.toTextDrawableString()
|
textView.text = text.toTextDrawableString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun colorFromIdentifier(key: String): Int {
|
private fun colorFromIdentifier(key: String): Int = colorScheme[abs(key.hashCode()) % colorScheme.size]
|
||||||
return colorScheme[abs(key.hashCode()) % colorScheme.size]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
|||||||
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
|
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
|
||||||
|
|
||||||
fun Context.openItemUrl(
|
fun Context.openItemUrl(
|
||||||
currentItem: Int,
|
currentItem: Int,
|
||||||
linkDecoded: String,
|
linkDecoded: String,
|
||||||
@ -26,11 +25,12 @@ fun Context.openItemUrl(
|
|||||||
app: Activity,
|
app: Activity,
|
||||||
) {
|
) {
|
||||||
if (!linkDecoded.isUrlValid()) {
|
if (!linkDecoded.isUrlValid()) {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this,
|
.makeText(
|
||||||
this.getString(R.string.cant_open_invalid_url),
|
this,
|
||||||
Toast.LENGTH_LONG,
|
this.getString(R.string.cant_open_invalid_url),
|
||||||
).show()
|
Toast.LENGTH_LONG,
|
||||||
|
).show()
|
||||||
} else {
|
} else {
|
||||||
if (articleViewer) {
|
if (articleViewer) {
|
||||||
val intent = Intent(this, ReaderActivity::class.java)
|
val intent = Intent(this, ReaderActivity::class.java)
|
||||||
@ -42,8 +42,7 @@ fun Context.openItemUrl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.isUrlValid(): Boolean =
|
fun String.isUrlValid(): Boolean = this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
|
||||||
this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
|
|
||||||
|
|
||||||
fun String.isBaseUrlInvalid(): Boolean {
|
fun String.isBaseUrlInvalid(): Boolean {
|
||||||
val baseUrl = this.toHttpUrlOrNull()
|
val baseUrl = this.toHttpUrlOrNull()
|
||||||
@ -61,7 +60,6 @@ fun Context.openItemUrlInBrowserAsNewTask(i: SelfossModel.Item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Context.openUrlInBrowserAsNewTask(url: String) {
|
fun Context.openUrlInBrowserAsNewTask(url: String) {
|
||||||
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW)
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
intent.data = Uri.parse(url)
|
intent.data = Uri.parse(url)
|
||||||
@ -74,13 +72,13 @@ fun Context.openUrlInBrowser(url: String) {
|
|||||||
this.mayBeStartActivity(intent)
|
this.mayBeStartActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:SwallowedException")
|
||||||
fun Context.mayBeStartActivity(intent: Intent) {
|
fun Context.mayBeStartActivity(intent: Intent) {
|
||||||
try {
|
try {
|
||||||
this.startActivity(intent)
|
this.startActivity(intent)
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
Toast.makeText(this, getString(R.string.no_browser), Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, getString(R.string.no_browser), Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class LinkOnTouchListener : View.OnTouchListener {
|
class LinkOnTouchListener : View.OnTouchListener {
|
||||||
|
@ -8,22 +8,19 @@ import org.acra.config.CoreConfiguration
|
|||||||
import org.acra.config.ReportingAdministrator
|
import org.acra.config.ReportingAdministrator
|
||||||
import org.acra.data.CrashReportData
|
import org.acra.data.CrashReportData
|
||||||
|
|
||||||
|
|
||||||
@AutoService(ReportingAdministrator::class)
|
@AutoService(ReportingAdministrator::class)
|
||||||
class AcraReportingAdministrator : ReportingAdministrator {
|
class AcraReportingAdministrator : ReportingAdministrator {
|
||||||
override fun shouldStartCollecting(
|
override fun shouldStartCollecting(
|
||||||
context: Context,
|
context: Context,
|
||||||
config: CoreConfiguration,
|
config: CoreConfiguration,
|
||||||
reportBuilder: ReportBuilder
|
reportBuilder: ReportBuilder,
|
||||||
): Boolean {
|
): Boolean =
|
||||||
return reportBuilder.exception !is DeadSystemException && (reportBuilder.exception != null && reportBuilder.exception!!::class.simpleName != "CannotDeliverBroadcastException")
|
reportBuilder.exception !is DeadSystemException &&
|
||||||
}
|
(reportBuilder.exception != null && reportBuilder.exception!!::class.simpleName != "CannotDeliverBroadcastException")
|
||||||
|
|
||||||
override fun shouldSendReport(
|
override fun shouldSendReport(
|
||||||
context: Context,
|
context: Context,
|
||||||
config: CoreConfiguration,
|
config: CoreConfiguration,
|
||||||
crashReportData: CrashReportData
|
crashReportData: CrashReportData,
|
||||||
): Boolean {
|
): Boolean = crashReportData.get("BRAND") != "redroid"
|
||||||
return crashReportData.get("BRAND") != "redroid"
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -13,7 +13,8 @@ import java.io.InputStream
|
|||||||
fun Context.bitmapCenterCrop(
|
fun Context.bitmapCenterCrop(
|
||||||
url: String,
|
url: String,
|
||||||
iv: ImageView,
|
iv: ImageView,
|
||||||
) = Glide.with(this)
|
) = Glide
|
||||||
|
.with(this)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(url)
|
.load(url)
|
||||||
.apply(RequestOptions.centerCropTransform())
|
.apply(RequestOptions.centerCropTransform())
|
||||||
@ -25,17 +26,20 @@ fun Context.circularDrawable(
|
|||||||
) {
|
) {
|
||||||
view.textView.text = ""
|
view.textView.text = ""
|
||||||
|
|
||||||
Glide.with(this)
|
Glide
|
||||||
|
.with(this)
|
||||||
.load(url)
|
.load(url)
|
||||||
.into(view.imageView)
|
.into(view.imageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val BITMAP_INPUT_STREAM_COMPRESSION_QUALITY = 80
|
||||||
|
|
||||||
fun getBitmapInputStream(
|
fun getBitmapInputStream(
|
||||||
bitmap: Bitmap,
|
bitmap: Bitmap,
|
||||||
compressFormat: Bitmap.CompressFormat,
|
compressFormat: Bitmap.CompressFormat,
|
||||||
): InputStream {
|
): InputStream {
|
||||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||||
bitmap.compress(compressFormat, 80, byteArrayOutputStream)
|
bitmap.compress(compressFormat, BITMAP_INPUT_STREAM_COMPRESSION_QUALITY, byteArrayOutputStream)
|
||||||
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
|
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
|
||||||
return ByteArrayInputStream(bitmapData)
|
return ByteArrayInputStream(bitmapData)
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package bou.amine.apps.readerforselfossv2.android.utils.network
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import android.os.Build
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
|
||||||
lateinit var s: Snackbar
|
lateinit var s: Snackbar
|
||||||
@ -11,19 +10,13 @@ lateinit var s: Snackbar
|
|||||||
fun isNetworkAccessible(context: Context): Boolean {
|
fun isNetworkAccessible(context: Context): Boolean {
|
||||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) ?: return false
|
||||||
val network = connectivityManager.activeNetwork ?: return false
|
|
||||||
val networkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
|
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val network = connectivityManager.activeNetworkInfo ?: return false
|
|
||||||
return network.isConnectedOrConnecting
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,9 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
|||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class AppViewModel(private val repository: Repository) : ViewModel() {
|
class AppViewModel(
|
||||||
|
private val repository: Repository,
|
||||||
|
) : ViewModel() {
|
||||||
private val _networkAvailableProvider = MutableSharedFlow<Boolean>()
|
private val _networkAvailableProvider = MutableSharedFlow<Boolean>()
|
||||||
val networkAvailableProvider = _networkAvailableProvider.asSharedFlow()
|
val networkAvailableProvider = _networkAvailableProvider.asSharedFlow()
|
||||||
private var wasConnected = true
|
private var wasConnected = true
|
||||||
@ -19,11 +21,10 @@ class AppViewModel(private val repository: Repository) : ViewModel() {
|
|||||||
if (isConnected && !wasConnected && repository.connectionMonitored) {
|
if (isConnected && !wasConnected && repository.connectionMonitored) {
|
||||||
_networkAvailableProvider.emit(true)
|
_networkAvailableProvider.emit(true)
|
||||||
wasConnected = true
|
wasConnected = true
|
||||||
} else if (!isConnected && wasConnected && repository.connectionMonitored)
|
} else if (!isConnected && wasConnected && repository.connectionMonitored) {
|
||||||
{
|
_networkAvailableProvider.emit(false)
|
||||||
_networkAvailableProvider.emit(false)
|
wasConnected = false
|
||||||
wasConnected = false
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<ListPreference
|
|
||||||
android:defaultValue="0"
|
|
||||||
android:entries="@array/ModeTitles"
|
|
||||||
android:entryValues="@array/ModeValues"
|
|
||||||
android:key="currentMode"
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
android:title="@string/pref_theme_title"
|
|
||||||
app:useSimpleSummaryProvider="false" />
|
|
||||||
</PreferenceScreen>
|
|
@ -11,13 +11,17 @@ fun dialogMessage(): String {
|
|||||||
return latestDialog.findViewById<TextView>(android.R.id.message)?.text.toString()
|
return latestDialog.findViewById<TextView>(android.R.id.message)?.text.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Menu.assertClickable(@IdRes id: Int) {
|
fun Menu.assertClickable(
|
||||||
|
@IdRes id: Int,
|
||||||
|
) {
|
||||||
this.assertVisible(id)
|
this.assertVisible(id)
|
||||||
val item = this.findItem(id)
|
val item = this.findItem(id)
|
||||||
assertTrue(item.isEnabled)
|
assertTrue(item.isEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Menu.assertVisible(@IdRes id: Int) {
|
fun Menu.assertVisible(
|
||||||
|
@IdRes id: Int,
|
||||||
|
) {
|
||||||
val item = this.findItem(id)
|
val item = this.findItem(id)
|
||||||
assertTrue(item.isVisible)
|
assertTrue(item.isVisible)
|
||||||
}
|
}
|
@ -11,10 +11,8 @@ import org.junit.Test
|
|||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.Robolectric
|
import org.robolectric.Robolectric
|
||||||
|
|
||||||
|
@RunWith(RobotElectriqueRunner::class)
|
||||||
@RunWith(RobotElectriqueRunnerclass::class)
|
|
||||||
class LoginActivityTest {
|
class LoginActivityTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun login_shouldDisplay() {
|
fun login_shouldDisplay() {
|
||||||
Robolectric.buildActivity(LoginActivity::class.java).use { controller ->
|
Robolectric.buildActivity(LoginActivity::class.java).use { controller ->
|
||||||
|
@ -3,10 +3,8 @@ package bou.amine.apps.readerforselfossv2.android.tests.robolectric
|
|||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
class RobotElectriqueRunnerclass(testClass: Class<*>?) :
|
class RobotElectriqueRunner(
|
||||||
RobolectricTestRunner(testClass) {
|
testClass: Class<*>?,
|
||||||
|
) : RobolectricTestRunner(testClass) {
|
||||||
override fun buildGlobalConfig(): Config {
|
override fun buildGlobalConfig(): Config = Config.Builder().setSdk(25, 30, 33).build()
|
||||||
return Config.Builder().setSdk(25, 30, 33).build()
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("detekt:LargeClass")
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.tests.repository
|
package bou.amine.apps.readerforselfossv2.tests.repository
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
||||||
@ -42,14 +44,14 @@ private const val FEED_URL = "https://test.com/feed"
|
|||||||
|
|
||||||
private const val TAGS = "Test, New"
|
private const val TAGS = "Test, New"
|
||||||
|
|
||||||
|
private const val NUMBER_ARTICLES = 100
|
||||||
|
private const val NUMBER_UNREAD = 50
|
||||||
|
private const val NUMBER_STARRED = 20
|
||||||
|
|
||||||
class RepositoryTest {
|
class RepositoryTest {
|
||||||
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
||||||
private val appSettingsService = mockk<AppSettingsService>()
|
private val appSettingsService = mockk<AppSettingsService>()
|
||||||
private val api = mockk<SelfossApi>()
|
private val api = mockk<SelfossApi>()
|
||||||
|
|
||||||
private val NUMBER_ARTICLES = 100
|
|
||||||
private val NUMBER_UNREAD = 50
|
|
||||||
private val NUMBER_STARRED = 20
|
|
||||||
private lateinit var repository: Repository
|
private lateinit var repository: Repository
|
||||||
|
|
||||||
private fun initializeRepository(
|
private fun initializeRepository(
|
||||||
@ -75,19 +77,20 @@ class RepositoryTest {
|
|||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||||
|
|
||||||
coEvery { api.apiInformation() } returns
|
coEvery { api.apiInformation() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = SelfossModel.ApiInformation(
|
data =
|
||||||
|
SelfossModel.ApiInformation(
|
||||||
"2.19-ba1e8e3",
|
"2.19-ba1e8e3",
|
||||||
"4.0.0",
|
"4.0.0",
|
||||||
SelfossModel.ApiConfiguration(false, true)
|
SelfossModel.ApiConfiguration(false, true),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
coEvery { api.stats() } returns
|
coEvery { api.stats() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED),
|
data = SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED),
|
||||||
)
|
)
|
||||||
|
|
||||||
every { db.itemsQueries.deleteItemsWhereSource(any()) } returns Unit
|
every { db.itemsQueries.deleteItemsWhereSource(any()) } returns Unit
|
||||||
every { db.itemsQueries.items().executeAsList() } returns generateTestDBItems()
|
every { db.itemsQueries.items().executeAsList() } returns generateTestDBItems()
|
||||||
@ -117,7 +120,7 @@ class RepositoryTest {
|
|||||||
fun get_api_4_date_with_api_1_version_stored() {
|
fun get_api_4_date_with_api_1_version_stored() {
|
||||||
every { appSettingsService.getApiVersion() } returns 1
|
every { appSettingsService.getApiVersion() } returns 1
|
||||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||||
StatusAndData(success = true, data = generateTestApiItem())
|
StatusAndData(success = true, data = generateTestApiItem())
|
||||||
every { appSettingsService.updateApiVersion(any()) } returns Unit
|
every { appSettingsService.updateApiVersion(any()) } returns Unit
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
@ -133,14 +136,15 @@ class RepositoryTest {
|
|||||||
fun get_public_access() {
|
fun get_public_access() {
|
||||||
every { appSettingsService.updatePublicAccess(any()) } returns Unit
|
every { appSettingsService.updatePublicAccess(any()) } returns Unit
|
||||||
coEvery { api.apiInformation() } returns
|
coEvery { api.apiInformation() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = SelfossModel.ApiInformation(
|
data =
|
||||||
|
SelfossModel.ApiInformation(
|
||||||
"2.19-ba1e8e3",
|
"2.19-ba1e8e3",
|
||||||
"4.0.0",
|
"4.0.0",
|
||||||
SelfossModel.ApiConfiguration(true, true)
|
SelfossModel.ApiConfiguration(true, true),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
every { appSettingsService.getUserName() } returns ""
|
every { appSettingsService.getUserName() } returns ""
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
@ -153,14 +157,15 @@ class RepositoryTest {
|
|||||||
fun get_public_access_username_not_empty() {
|
fun get_public_access_username_not_empty() {
|
||||||
every { appSettingsService.updatePublicAccess(any()) } returns Unit
|
every { appSettingsService.updatePublicAccess(any()) } returns Unit
|
||||||
coEvery { api.apiInformation() } returns
|
coEvery { api.apiInformation() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = SelfossModel.ApiInformation(
|
data =
|
||||||
|
SelfossModel.ApiInformation(
|
||||||
"2.19-ba1e8e3",
|
"2.19-ba1e8e3",
|
||||||
"4.0.0",
|
"4.0.0",
|
||||||
SelfossModel.ApiConfiguration(true, true)
|
SelfossModel.ApiConfiguration(true, true),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
every { appSettingsService.getUserName() } returns "username"
|
every { appSettingsService.getUserName() } returns "username"
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
@ -173,14 +178,15 @@ class RepositoryTest {
|
|||||||
fun get_public_access_no_auth() {
|
fun get_public_access_no_auth() {
|
||||||
every { appSettingsService.updatePublicAccess(any()) } returns Unit
|
every { appSettingsService.updatePublicAccess(any()) } returns Unit
|
||||||
coEvery { api.apiInformation() } returns
|
coEvery { api.apiInformation() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = SelfossModel.ApiInformation(
|
data =
|
||||||
|
SelfossModel.ApiInformation(
|
||||||
"2.19-ba1e8e3",
|
"2.19-ba1e8e3",
|
||||||
"4.0.0",
|
"4.0.0",
|
||||||
SelfossModel.ApiConfiguration(true, false)
|
SelfossModel.ApiConfiguration(true, false),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
every { appSettingsService.getUserName() } returns ""
|
every { appSettingsService.getUserName() } returns ""
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
@ -193,14 +199,15 @@ class RepositoryTest {
|
|||||||
fun get_public_access_disabled() {
|
fun get_public_access_disabled() {
|
||||||
every { appSettingsService.updatePublicAccess(any()) } returns Unit
|
every { appSettingsService.updatePublicAccess(any()) } returns Unit
|
||||||
coEvery { api.apiInformation() } returns
|
coEvery { api.apiInformation() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = SelfossModel.ApiInformation(
|
data =
|
||||||
|
SelfossModel.ApiInformation(
|
||||||
"2.19-ba1e8e3",
|
"2.19-ba1e8e3",
|
||||||
"4.0.0",
|
"4.0.0",
|
||||||
SelfossModel.ApiConfiguration(false, true)
|
SelfossModel.ApiConfiguration(false, true),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
every { appSettingsService.getUserName() } returns ""
|
every { appSettingsService.getUserName() } returns ""
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
@ -216,10 +223,10 @@ class RepositoryTest {
|
|||||||
val itemParameters = FakeItemParameters()
|
val itemParameters = FakeItemParameters()
|
||||||
itemParameters.datetime = "2021-04-23 11:45:32"
|
itemParameters.datetime = "2021-04-23 11:45:32"
|
||||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = generateTestApiItem(itemParameters),
|
data = generateTestApiItem(itemParameters),
|
||||||
)
|
)
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
@ -232,7 +239,7 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun get_newer_items() {
|
fun get_newer_items() {
|
||||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||||
StatusAndData(success = true, data = generateTestApiItem())
|
StatusAndData(success = true, data = generateTestApiItem())
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
@ -247,7 +254,7 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun get_all_newer_items() {
|
fun get_all_newer_items() {
|
||||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||||
StatusAndData(success = true, data = generateTestApiItem())
|
StatusAndData(success = true, data = generateTestApiItem())
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
repository.displayedItems = ItemType.ALL
|
repository.displayedItems = ItemType.ALL
|
||||||
@ -263,7 +270,7 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun get_newer_starred_items() {
|
fun get_newer_starred_items() {
|
||||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||||
StatusAndData(success = true, data = generateTestApiItem())
|
StatusAndData(success = true, data = generateTestApiItem())
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
repository.displayedItems = ItemType.STARRED
|
repository.displayedItems = ItemType.STARRED
|
||||||
@ -302,8 +309,8 @@ class RepositoryTest {
|
|||||||
coEvery { db.itemsQueries.items().executeAsList() } returns generateTestDBItems(
|
coEvery { db.itemsQueries.items().executeAsList() } returns generateTestDBItems(
|
||||||
itemParameter1,
|
itemParameter1,
|
||||||
) +
|
) +
|
||||||
generateTestDBItems(itemParameter2) +
|
generateTestDBItems(itemParameter2) +
|
||||||
generateTestDBItems(itemParameter3)
|
generateTestDBItems(itemParameter3)
|
||||||
|
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||||
|
|
||||||
@ -330,8 +337,8 @@ class RepositoryTest {
|
|||||||
coEvery { db.itemsQueries.items().executeAsList() } returns generateTestDBItems(
|
coEvery { db.itemsQueries.items().executeAsList() } returns generateTestDBItems(
|
||||||
itemParameter1,
|
itemParameter1,
|
||||||
) +
|
) +
|
||||||
generateTestDBItems(itemParameter2) +
|
generateTestDBItems(itemParameter2) +
|
||||||
generateTestDBItems(itemParameter3)
|
generateTestDBItems(itemParameter3)
|
||||||
|
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||||
|
|
||||||
@ -360,7 +367,7 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun get_older_items() {
|
fun get_older_items() {
|
||||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||||
StatusAndData(success = true, data = generateTestApiItem())
|
StatusAndData(success = true, data = generateTestApiItem())
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
repository.items = ArrayList(generateTestApiItem())
|
repository.items = ArrayList(generateTestApiItem())
|
||||||
@ -376,7 +383,7 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun get_all_older_items() {
|
fun get_all_older_items() {
|
||||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||||
StatusAndData(success = true, data = generateTestApiItem())
|
StatusAndData(success = true, data = generateTestApiItem())
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
repository.items = ArrayList(generateTestApiItem())
|
repository.items = ArrayList(generateTestApiItem())
|
||||||
@ -393,7 +400,7 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun get_older_starred_items() {
|
fun get_older_starred_items() {
|
||||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||||
StatusAndData(success = true, data = generateTestApiItem())
|
StatusAndData(success = true, data = generateTestApiItem())
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
repository.displayedItems = ItemType.STARRED
|
repository.displayedItems = ItemType.STARRED
|
||||||
@ -833,7 +840,7 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun create_source() {
|
fun create_source() {
|
||||||
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
|
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
|
||||||
SuccessResponse(true)
|
SuccessResponse(true)
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
var response: Boolean
|
var response: Boolean
|
||||||
@ -861,7 +868,7 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun create_source_but_response_fails() {
|
fun create_source_but_response_fails() {
|
||||||
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
|
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
|
||||||
SuccessResponse(false)
|
SuccessResponse(false)
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
var response: Boolean
|
var response: Boolean
|
||||||
@ -889,7 +896,7 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun create_source_without_connection() {
|
fun create_source_without_connection() {
|
||||||
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
|
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
|
||||||
SuccessResponse(true)
|
SuccessResponse(true)
|
||||||
|
|
||||||
initializeRepository(MutableStateFlow(false))
|
initializeRepository(MutableStateFlow(false))
|
||||||
var response: Boolean
|
var response: Boolean
|
||||||
@ -962,10 +969,10 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun update_remote() {
|
fun update_remote() {
|
||||||
coEvery { api.update() } returns
|
coEvery { api.update() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = "finished",
|
data = "finished",
|
||||||
)
|
)
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
var response: Boolean
|
var response: Boolean
|
||||||
@ -980,10 +987,10 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun update_remote_but_response_fails() {
|
fun update_remote_but_response_fails() {
|
||||||
coEvery { api.update() } returns
|
coEvery { api.update() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = false,
|
success = false,
|
||||||
data = "unallowed access",
|
data = "unallowed access",
|
||||||
)
|
)
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
var response: Boolean
|
var response: Boolean
|
||||||
@ -998,10 +1005,10 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun update_remote_with_unallowed_access() {
|
fun update_remote_with_unallowed_access() {
|
||||||
coEvery { api.update() } returns
|
coEvery { api.update() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = "unallowed access",
|
data = "unallowed access",
|
||||||
)
|
)
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
var response: Boolean
|
var response: Boolean
|
||||||
@ -1016,10 +1023,10 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun update_remote_without_connection() {
|
fun update_remote_without_connection() {
|
||||||
coEvery { api.update() } returns
|
coEvery { api.update() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = "undocumented...",
|
data = "undocumented...",
|
||||||
)
|
)
|
||||||
|
|
||||||
initializeRepository(MutableStateFlow(false))
|
initializeRepository(MutableStateFlow(false))
|
||||||
var response: Boolean
|
var response: Boolean
|
||||||
@ -1109,11 +1116,11 @@ class RepositoryTest {
|
|||||||
any(),
|
any(),
|
||||||
)
|
)
|
||||||
} returnsMany
|
} returnsMany
|
||||||
listOf(
|
listOf(
|
||||||
StatusAndData(success = true, data = generateTestApiItem(itemParameter1)),
|
StatusAndData(success = true, data = generateTestApiItem(itemParameter1)),
|
||||||
StatusAndData(success = true, data = generateTestApiItem(itemParameter2)),
|
StatusAndData(success = true, data = generateTestApiItem(itemParameter2)),
|
||||||
StatusAndData(success = true, data = generateTestApiItem(itemParameter1)),
|
StatusAndData(success = true, data = generateTestApiItem(itemParameter1)),
|
||||||
)
|
)
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
prepareSearch()
|
prepareSearch()
|
||||||
@ -1127,7 +1134,7 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun cache_items_but_response_fails() {
|
fun cache_items_but_response_fails() {
|
||||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||||
StatusAndData(success = false, data = generateTestApiItem())
|
StatusAndData(success = false, data = generateTestApiItem())
|
||||||
|
|
||||||
initializeRepository()
|
initializeRepository()
|
||||||
prepareSearch()
|
prepareSearch()
|
||||||
@ -1141,7 +1148,7 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun cache_items_without_connection() {
|
fun cache_items_without_connection() {
|
||||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||||
StatusAndData(success = false, data = generateTestApiItem())
|
StatusAndData(success = false, data = generateTestApiItem())
|
||||||
|
|
||||||
initializeRepository(MutableStateFlow(false))
|
initializeRepository(MutableStateFlow(false))
|
||||||
prepareSearch()
|
prepareSearch()
|
||||||
|
@ -3,8 +3,8 @@ package bou.amine.apps.readerforselfossv2.tests.repository
|
|||||||
import bou.amine.apps.readerforselfossv2.dao.ITEM
|
import bou.amine.apps.readerforselfossv2.dao.ITEM
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
|
|
||||||
fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> {
|
fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> =
|
||||||
return listOf(
|
listOf(
|
||||||
ITEM(
|
ITEM(
|
||||||
id = item.id,
|
id = item.id,
|
||||||
datetime = item.datetime,
|
datetime = item.datetime,
|
||||||
@ -20,10 +20,9 @@ fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<I
|
|||||||
author = item.author,
|
author = item.author,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<SelfossModel.Item> {
|
fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<SelfossModel.Item> =
|
||||||
return listOf(
|
listOf(
|
||||||
SelfossModel.Item(
|
SelfossModel.Item(
|
||||||
id = item.id.toInt(),
|
id = item.id.toInt(),
|
||||||
datetime = item.datetime,
|
datetime = item.datetime,
|
||||||
@ -39,7 +38,6 @@ fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<S
|
|||||||
author = item.author,
|
author = item.author,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
class FakeItemParameters {
|
class FakeItemParameters {
|
||||||
var id = "20"
|
var id = "20"
|
||||||
|
786
detekt.yml
Normal file
786
detekt.yml
Normal file
@ -0,0 +1,786 @@
|
|||||||
|
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.*'
|
@ -4,12 +4,13 @@ import android.content.Context
|
|||||||
import app.cash.sqldelight.db.SqlDriver
|
import app.cash.sqldelight.db.SqlDriver
|
||||||
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
||||||
|
|
||||||
actual class DriverFactory(private val context: Context) {
|
actual class DriverFactory(
|
||||||
actual fun createDriver(): SqlDriver {
|
private val context: Context,
|
||||||
return AndroidSqliteDriver(
|
) {
|
||||||
|
actual fun createDriver(): SqlDriver =
|
||||||
|
AndroidSqliteDriver(
|
||||||
ReaderForSelfossDB.Schema,
|
ReaderForSelfossDB.Schema,
|
||||||
context,
|
context,
|
||||||
"ReaderForSelfossV2-android.db"
|
"ReaderForSelfossV2-android.db",
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
@ -8,16 +8,20 @@ class NaiveTrustManager : X509TrustManager {
|
|||||||
override fun checkClientTrusted(
|
override fun checkClientTrusted(
|
||||||
chain: Array<out X509Certificate>?,
|
chain: Array<out X509Certificate>?,
|
||||||
authType: String?,
|
authType: String?,
|
||||||
) {}
|
) {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
override fun checkServerTrusted(
|
override fun checkServerTrusted(
|
||||||
chain: Array<out X509Certificate>?,
|
chain: Array<out X509Certificate>?,
|
||||||
authType: String?,
|
authType: String?,
|
||||||
) {}
|
) {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
|
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) {
|
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
||||||
config.https.trustManager = NaiveTrustManager()
|
config.https.trustManager = NaiveTrustManager()
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import io.github.aakira.napier.Napier
|
import kotlinx.datetime.Clock
|
||||||
import kotlinx.datetime.*
|
|
||||||
|
|
||||||
|
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
||||||
actual class DateUtils {
|
actual class DateUtils {
|
||||||
actual companion object {
|
actual companion object {
|
||||||
actual fun parseRelativeDate(dateString: String): String {
|
actual fun parseRelativeDate(dateString: String): String {
|
||||||
|
@ -4,46 +4,36 @@ import android.net.Uri
|
|||||||
import android.text.Html
|
import android.text.Html
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
|
||||||
actual fun String.getHtmlDecoded(): String {
|
actual fun String.getHtmlDecoded(): String = Html.fromHtml(this).toString()
|
||||||
return Html.fromHtml(this).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun SelfossModel.Item.getIcon(baseUrl: String): String {
|
actual fun SelfossModel.Item.getIcon(baseUrl: String): String = constructUrl(baseUrl, "favicons", icon)
|
||||||
return constructUrl(baseUrl, "favicons", icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String {
|
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String = constructUrl(baseUrl, "thumbnails", thumbnail)
|
||||||
return constructUrl(baseUrl, "thumbnails", thumbnail)
|
|
||||||
}
|
val IMAGE_EXTENSION_REGEXP = """\.(jpg|jpeg|png|webp)""".toRegex()
|
||||||
|
|
||||||
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
||||||
val allImages = ArrayList<String>()
|
val allImages = ArrayList<String>()
|
||||||
|
|
||||||
for (image in Jsoup.parse(content).getElementsByTag("img")) {
|
for (image in Jsoup.parse(content).getElementsByTag("img")) {
|
||||||
val url = image.attr("src")
|
val url = image.attr("src")
|
||||||
if (url.lowercase(Locale.US).contains(".jpg") ||
|
if (IMAGE_EXTENSION_REGEXP.containsMatchIn(url.lowercase(Locale.US))) {
|
||||||
url.lowercase(Locale.US).contains(".jpeg") ||
|
|
||||||
url.lowercase(Locale.US).contains(".png") ||
|
|
||||||
url.lowercase(Locale.US).contains(".webp")
|
|
||||||
) {
|
|
||||||
allImages.add(url)
|
allImages.add(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return allImages
|
return allImages
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun SelfossModel.Source.getIcon(baseUrl: String): String {
|
actual fun SelfossModel.Source.getIcon(baseUrl: String): String = constructUrl(baseUrl, "favicons", icon)
|
||||||
return constructUrl(baseUrl, "favicons", icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun constructUrl(
|
actual fun constructUrl(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
path: String,
|
path: String,
|
||||||
file: String?,
|
file: String?,
|
||||||
): String {
|
): String =
|
||||||
return if (file == null || file == "null" || file.isEmpty()) {
|
if (file == null || file == "null" || file.isEmpty()) {
|
||||||
""
|
""
|
||||||
} else {
|
} else {
|
||||||
val baseUriBuilder = Uri.parse(baseUrl).buildUpon()
|
val baseUriBuilder = Uri.parse(baseUrl).buildUpon()
|
||||||
@ -51,4 +41,3 @@ actual fun constructUrl(
|
|||||||
|
|
||||||
baseUriBuilder.toString()
|
baseUriBuilder.toString()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.DI
|
package bou.amine.apps.readerforselfossv2.di
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
|
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
@ -1,13 +1,16 @@
|
|||||||
|
@file:Suppress("detekt:LongParameterList")
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.model
|
package bou.amine.apps.readerforselfossv2.model
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
class MercuryModel {
|
class MercuryModel {
|
||||||
|
@Suppress("detekt:ConstructorParameterNaming")
|
||||||
@Serializable
|
@Serializable
|
||||||
class ParsedContent(
|
class ParsedContent(
|
||||||
val title: String? = null,
|
val title: String? = null,
|
||||||
val content: String? = null,
|
val content: String? = null,
|
||||||
val lead_image_url: String? = null, // NOSONAR
|
val lead_image_url: String? = null,
|
||||||
val url: String? = null,
|
val url: String? = null,
|
||||||
val error: Boolean? = null,
|
val error: Boolean? = null,
|
||||||
val message: String? = null,
|
val message: String? = null,
|
||||||
|
@ -3,19 +3,20 @@ package bou.amine.apps.readerforselfossv2.model
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class SuccessResponse(val success: Boolean) {
|
class SuccessResponse(
|
||||||
|
val success: Boolean,
|
||||||
|
) {
|
||||||
val isSuccess: Boolean
|
val isSuccess: Boolean
|
||||||
get() = success
|
get() = success
|
||||||
}
|
}
|
||||||
|
|
||||||
class StatusAndData<T>(val success: Boolean, val data: T? = null) {
|
class StatusAndData<T>(
|
||||||
|
val success: Boolean,
|
||||||
|
val data: T? = null,
|
||||||
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun <T> succes(d: T): StatusAndData<T> {
|
fun <T> succes(d: T): StatusAndData<T> = StatusAndData(true, d)
|
||||||
return StatusAndData(true, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> error(): StatusAndData<T> {
|
fun <T> error(): StatusAndData<T> = StatusAndData(false)
|
||||||
return StatusAndData(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("detekt:LongParameterList")
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.model
|
package bou.amine.apps.readerforselfossv2.model
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||||
@ -18,6 +20,10 @@ import kotlinx.serialization.json.booleanOrNull
|
|||||||
import kotlinx.serialization.json.int
|
import kotlinx.serialization.json.int
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
|
||||||
|
class ModelException(
|
||||||
|
message: String,
|
||||||
|
) : Throwable(message)
|
||||||
|
|
||||||
class SelfossModel {
|
class SelfossModel {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Tag(
|
data class Tag(
|
||||||
@ -141,7 +147,7 @@ class SelfossModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stringUrl.isEmptyOrNullOrNullString()) {
|
if (stringUrl.isEmptyOrNullOrNullString()) {
|
||||||
throw Exception("Link ${link} was translated to ${stringUrl}, but was empty. Handle this.")
|
throw ModelException("Link $link was translated to $stringUrl, but was empty. Handle this.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return stringUrl
|
return stringUrl
|
||||||
@ -170,14 +176,13 @@ class SelfossModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this seems to be super slow.
|
// this seems to be super slow.
|
||||||
object TagsListSerializer : KSerializer<List<String>> {
|
object TagsListSerializer : KSerializer<List<String>> {
|
||||||
override fun deserialize(decoder: Decoder): List<String> {
|
override fun deserialize(decoder: Decoder): List<String> =
|
||||||
return when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {
|
when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {
|
||||||
is JsonArray -> json.toList().map { it.toString().replace("^\"|\"$".toRegex(), "") }
|
is JsonArray -> json.toList().map { it.toString().replace("^\"|\"$".toRegex(), "") }
|
||||||
else -> json.toString().split(",")
|
else -> json.toString().split(",")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override val descriptor: SerialDescriptor
|
override val descriptor: SerialDescriptor
|
||||||
get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
|
get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
|
||||||
@ -188,7 +193,7 @@ class SelfossModel {
|
|||||||
) {
|
) {
|
||||||
encoder.encodeCollection(
|
encoder.encodeCollection(
|
||||||
PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING),
|
PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING),
|
||||||
value.size
|
value.size,
|
||||||
) { this.toString() }
|
) { this.toString() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,10 +209,11 @@ class SelfossModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val descriptor: SerialDescriptor
|
override val descriptor: SerialDescriptor
|
||||||
get() = PrimitiveSerialDescriptor(
|
get() =
|
||||||
"BooleanOrIntForSomeSelfossVersions",
|
PrimitiveSerialDescriptor(
|
||||||
PrimitiveKind.BOOLEAN
|
"BooleanOrIntForSomeSelfossVersions",
|
||||||
)
|
PrimitiveKind.BOOLEAN,
|
||||||
|
)
|
||||||
|
|
||||||
override fun serialize(
|
override fun serialize(
|
||||||
encoder: Encoder,
|
encoder: Encoder,
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
|
@file:Suppress("detekt:TooManyFunctions")
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.repository
|
package bou.amine.apps.readerforselfossv2.repository
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.dao.*
|
import bou.amine.apps.readerforselfossv2.dao.ACTION
|
||||||
|
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
|
||||||
|
import bou.amine.apps.readerforselfossv2.dao.ITEM
|
||||||
|
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
||||||
|
import bou.amine.apps.readerforselfossv2.dao.SOURCE
|
||||||
|
import bou.amine.apps.readerforselfossv2.dao.TAG
|
||||||
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
|
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import bou.amine.apps.readerforselfossv2.utils.*
|
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.toEntity
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.toParsedDate
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.toView
|
||||||
import io.github.aakira.napier.Napier
|
import io.github.aakira.napier.Napier
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -14,6 +25,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
private const val MAX_ITEMS_NUMBER = 200
|
||||||
|
|
||||||
class Repository(
|
class Repository(
|
||||||
private val api: SelfossApi,
|
private val api: SelfossApi,
|
||||||
private val appSettingsService: AppSettingsService,
|
private val appSettingsService: AppSettingsService,
|
||||||
@ -118,7 +131,7 @@ class Repository(
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
200,
|
MAX_ITEMS_NUMBER,
|
||||||
)
|
)
|
||||||
return if (items.success && items.data != null) {
|
return if (items.success && items.data != null) {
|
||||||
items.data
|
items.data
|
||||||
@ -130,6 +143,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:ForbiddenComment")
|
||||||
suspend fun reloadBadges(): Boolean {
|
suspend fun reloadBadges(): Boolean {
|
||||||
var success = false
|
var success = false
|
||||||
if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
@ -170,8 +184,8 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSpouts(): Map<String, SelfossModel.Spout> {
|
suspend fun getSpouts(): Map<String, SelfossModel.Spout> =
|
||||||
return if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
val spouts = api.spouts()
|
val spouts = api.spouts()
|
||||||
if (spouts.success && spouts.data != null) {
|
if (spouts.success && spouts.data != null) {
|
||||||
spouts.data
|
spouts.data
|
||||||
@ -181,7 +195,6 @@ class Repository(
|
|||||||
} else {
|
} else {
|
||||||
throw NetworkUnavailableException()
|
throw NetworkUnavailableException()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getSourcesDetailsOrStats(): ArrayList<SelfossModel.Source> {
|
suspend fun getSourcesDetailsOrStats(): ArrayList<SelfossModel.Source> {
|
||||||
var sources = ArrayList<SelfossModel.Source>()
|
var sources = ArrayList<SelfossModel.Source>()
|
||||||
@ -234,14 +247,13 @@ class Repository(
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun markAsReadById(id: Int): Boolean {
|
private suspend fun markAsReadById(id: Int): Boolean =
|
||||||
return if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.markAsRead(id.toString()).isSuccess
|
api.markAsRead(id.toString()).isSuccess
|
||||||
} else {
|
} else {
|
||||||
insertDBAction(id.toString(), read = true)
|
insertDBAction(id.toString(), read = true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
|
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
|
||||||
val success = unmarkAsReadById(item.id)
|
val success = unmarkAsReadById(item.id)
|
||||||
@ -252,14 +264,13 @@ class Repository(
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun unmarkAsReadById(id: Int): Boolean {
|
private suspend fun unmarkAsReadById(id: Int): Boolean =
|
||||||
return if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.unmarkAsRead(id.toString()).isSuccess
|
api.unmarkAsRead(id.toString()).isSuccess
|
||||||
} else {
|
} else {
|
||||||
insertDBAction(id.toString(), unread = true)
|
insertDBAction(id.toString(), unread = true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun starr(item: SelfossModel.Item): Boolean {
|
suspend fun starr(item: SelfossModel.Item): Boolean {
|
||||||
val success = starrById(item.id)
|
val success = starrById(item.id)
|
||||||
@ -270,14 +281,13 @@ class Repository(
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun starrById(id: Int): Boolean {
|
private suspend fun starrById(id: Int): Boolean =
|
||||||
return if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.starr(id.toString()).isSuccess
|
api.starr(id.toString()).isSuccess
|
||||||
} else {
|
} else {
|
||||||
insertDBAction(id.toString(), starred = true)
|
insertDBAction(id.toString(), starred = true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun unstarr(item: SelfossModel.Item): Boolean {
|
suspend fun unstarr(item: SelfossModel.Item): Boolean {
|
||||||
val success = unstarrById(item.id)
|
val success = unstarrById(item.id)
|
||||||
@ -288,14 +298,13 @@ class Repository(
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun unstarrById(id: Int): Boolean {
|
private suspend fun unstarrById(id: Int): Boolean =
|
||||||
return if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.unstarr(id.toString()).isSuccess
|
api.unstarr(id.toString()).isSuccess
|
||||||
} else {
|
} else {
|
||||||
insertDBAction(id.toString(), starred = true)
|
insertDBAction(id.toString(), starred = true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
||||||
var success = false
|
var success = false
|
||||||
@ -361,12 +370,13 @@ class Repository(
|
|||||||
): Boolean {
|
): Boolean {
|
||||||
var response = false
|
var response = false
|
||||||
if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
response = api.createSourceForVersion(
|
response = api
|
||||||
title,
|
.createSourceForVersion(
|
||||||
url,
|
title,
|
||||||
spout,
|
url,
|
||||||
tags,
|
spout,
|
||||||
).isSuccess == true
|
tags,
|
||||||
|
).isSuccess == true
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@ -407,13 +417,12 @@ class Repository(
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateRemote(): Boolean {
|
suspend fun updateRemote(): Boolean =
|
||||||
return if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.update().data.equals("finished")
|
api.update().data.equals("finished")
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun login(): Boolean {
|
suspend fun login(): Boolean {
|
||||||
var result = false
|
var result = false
|
||||||
@ -422,7 +431,7 @@ class Repository(
|
|||||||
val response = api.login()
|
val response = api.login()
|
||||||
result = response.isSuccess == true
|
result = response.isSuccess == true
|
||||||
} catch (cause: Throwable) {
|
} catch (cause: Throwable) {
|
||||||
Napier.e("login failed", cause, tag = "RepositoryImpl.login")
|
Napier.e("login failed", cause, tag = "Repository.login")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@ -436,7 +445,7 @@ class Repository(
|
|||||||
// a random rss feed, that would throw a NoTransformationFoundException
|
// a random rss feed, that would throw a NoTransformationFoundException
|
||||||
fetchFailed = !api.getItemsWithoutCatch().success
|
fetchFailed = !api.getItemsWithoutCatch().success
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Napier.e("checkIfFetchFails failed", e, tag = "RepositoryImpl.shouldBeSelfossInstance")
|
Napier.e("checkIfFetchFails failed", e, tag = "Repository.shouldBeSelfossInstance")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,10 +457,10 @@ class Repository(
|
|||||||
try {
|
try {
|
||||||
val response = api.logout()
|
val response = api.logout()
|
||||||
if (!response.isSuccess) {
|
if (!response.isSuccess) {
|
||||||
Napier.e("Couldn't logout.", tag = "RepositoryImpl.logout")
|
Napier.e("Couldn't logout.", tag = "Repository.logout")
|
||||||
}
|
}
|
||||||
} catch (cause: Throwable) {
|
} catch (cause: Throwable) {
|
||||||
Napier.e("logout failed", cause, tag = "RepositoryImpl.logout")
|
Napier.e("logout failed", cause, tag = "Repository.logout")
|
||||||
}
|
}
|
||||||
appSettingsService.clearAll()
|
appSettingsService.clearAll()
|
||||||
} else {
|
} else {
|
||||||
@ -555,6 +564,7 @@ class Repository(
|
|||||||
item.id.toString(),
|
item.id.toString(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Suppress("detekt:SwallowedException")
|
||||||
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
|
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
|
||||||
try {
|
try {
|
||||||
val newItems = getMaxItemsForBackground(ItemType.UNREAD)
|
val newItems = getMaxItemsForBackground(ItemType.UNREAD)
|
||||||
@ -578,16 +588,19 @@ class Repository(
|
|||||||
markAsReadById(action.articleid.toInt()),
|
markAsReadById(action.articleid.toInt()),
|
||||||
action,
|
action,
|
||||||
)
|
)
|
||||||
|
|
||||||
action.unread ->
|
action.unread ->
|
||||||
doAndReportOnFail(
|
doAndReportOnFail(
|
||||||
unmarkAsReadById(action.articleid.toInt()),
|
unmarkAsReadById(action.articleid.toInt()),
|
||||||
action,
|
action,
|
||||||
)
|
)
|
||||||
|
|
||||||
action.starred ->
|
action.starred ->
|
||||||
doAndReportOnFail(
|
doAndReportOnFail(
|
||||||
starrById(action.articleid.toInt()),
|
starrById(action.articleid.toInt()),
|
||||||
action,
|
action,
|
||||||
)
|
)
|
||||||
|
|
||||||
action.unstarred ->
|
action.unstarred ->
|
||||||
doAndReportOnFail(
|
doAndReportOnFail(
|
||||||
unstarrById(action.articleid.toInt()),
|
unstarrById(action.articleid.toInt()),
|
||||||
@ -618,9 +631,7 @@ class Repository(
|
|||||||
_readerItems = readerItems
|
_readerItems = readerItems
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getReaderItems(): ArrayList<SelfossModel.Item> {
|
fun getReaderItems(): ArrayList<SelfossModel.Item> = _readerItems
|
||||||
return _readerItems
|
|
||||||
}
|
|
||||||
|
|
||||||
fun migrate(driverFactory: DriverFactory) {
|
fun migrate(driverFactory: DriverFactory) {
|
||||||
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
||||||
@ -634,7 +645,5 @@ class Repository(
|
|||||||
_selectedSource = null
|
_selectedSource = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSelectedSource(): SelfossModel.SourceDetail? {
|
fun getSelectedSource(): SelfossModel.SourceDetail? = _selectedSource
|
||||||
return _selectedSource
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -17,8 +17,8 @@ import kotlinx.serialization.json.Json
|
|||||||
class MercuryApi {
|
class MercuryApi {
|
||||||
var client = createHttpClient()
|
var client = createHttpClient()
|
||||||
|
|
||||||
private fun createHttpClient(): HttpClient {
|
private fun createHttpClient(): HttpClient =
|
||||||
return HttpClient {
|
HttpClient {
|
||||||
install(HttpCache)
|
install(HttpCache)
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json(
|
json(
|
||||||
@ -40,7 +40,6 @@ class MercuryApi {
|
|||||||
}
|
}
|
||||||
expectSuccess = false
|
expectSuccess = false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> =
|
suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> =
|
||||||
bodyOrFailure(
|
bodyOrFailure(
|
||||||
|
@ -3,23 +3,28 @@ package bou.amine.apps.readerforselfossv2.rest
|
|||||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
||||||
import bou.amine.apps.readerforselfossv2.model.SuccessResponse
|
import bou.amine.apps.readerforselfossv2.model.SuccessResponse
|
||||||
import io.github.aakira.napier.Napier
|
import io.github.aakira.napier.Napier
|
||||||
import io.ktor.client.*
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.body
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.HttpRequestBuilder
|
||||||
import io.ktor.client.request.forms.*
|
import io.ktor.client.request.delete
|
||||||
import io.ktor.client.statement.*
|
import io.ktor.client.request.forms.submitForm
|
||||||
import io.ktor.http.*
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.client.request.post
|
||||||
|
import io.ktor.client.request.url
|
||||||
|
import io.ktor.client.statement.HttpResponse
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.http.Parameters
|
||||||
|
import io.ktor.http.isSuccess
|
||||||
|
|
||||||
suspend fun responseOrSuccessIf404(r: HttpResponse?): SuccessResponse {
|
suspend fun responseOrSuccessIf404(r: HttpResponse?): SuccessResponse =
|
||||||
return if (r != null && r.status === HttpStatusCode.NotFound) {
|
if (r != null && r.status === HttpStatusCode.NotFound) {
|
||||||
SuccessResponse(true)
|
SuccessResponse(true)
|
||||||
} else {
|
} else {
|
||||||
maybeResponse(r)
|
maybeResponse(r)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun maybeResponse(r: HttpResponse?): SuccessResponse {
|
suspend fun maybeResponse(r: HttpResponse?): SuccessResponse =
|
||||||
return if (r != null && r.status.isSuccess()) {
|
if (r != null && r.status.isSuccess()) {
|
||||||
r.body()
|
r.body()
|
||||||
} else {
|
} else {
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
@ -27,8 +32,8 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse {
|
|||||||
}
|
}
|
||||||
SuccessResponse(false)
|
SuccessResponse(false)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
@Suppress("detekt:SwallowedException")
|
||||||
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
|
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
|
||||||
try {
|
try {
|
||||||
return if (r != null && r.status.isSuccess()) {
|
return if (r != null && r.status.isSuccess()) {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("detekt:TooManyFunctions", "detekt:LongParameterList", "detekt:LargeClass")
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.rest
|
package bou.amine.apps.readerforselfossv2.rest
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
@ -33,16 +35,20 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
expect fun setupInsecureHTTPEngine(config: CIOEngineConfig)
|
expect fun setupInsecureHttpEngine(config: CIOEngineConfig)
|
||||||
|
|
||||||
class SelfossApi(private val appSettingsService: AppSettingsService) {
|
private const val VERSION_WHERE_POST_LOGIN_SHOULD_WORK = 5
|
||||||
|
|
||||||
|
class SelfossApi(
|
||||||
|
private val appSettingsService: AppSettingsService,
|
||||||
|
) {
|
||||||
var client = createHttpClient()
|
var client = createHttpClient()
|
||||||
|
|
||||||
fun createHttpClient() =
|
fun createHttpClient() =
|
||||||
HttpClient(CIO) {
|
HttpClient(CIO) {
|
||||||
if (appSettingsService.getSelfSigned()) {
|
if (appSettingsService.getSelfSigned()) {
|
||||||
engine {
|
engine {
|
||||||
setupInsecureHTTPEngine(this)
|
setupInsecureHttpEngine(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
install(HttpCache)
|
install(HttpCache)
|
||||||
@ -105,12 +111,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
|
|
||||||
private fun hasLoginInfo() =
|
private fun hasLoginInfo() =
|
||||||
appSettingsService.getUserName().isNotEmpty() &&
|
appSettingsService.getUserName().isNotEmpty() &&
|
||||||
appSettingsService.getPassword()
|
appSettingsService
|
||||||
.isNotEmpty()
|
.getPassword()
|
||||||
|
.isNotEmpty()
|
||||||
|
|
||||||
suspend fun login(): SuccessResponse =
|
suspend fun login(): SuccessResponse =
|
||||||
if (appSettingsService.getUserName().isNotEmpty() &&
|
if (appSettingsService.getUserName().isNotEmpty() &&
|
||||||
appSettingsService.getPassword()
|
appSettingsService
|
||||||
|
.getPassword()
|
||||||
.isNotEmpty()
|
.isNotEmpty()
|
||||||
) {
|
) {
|
||||||
if (shouldHavePostLogin()) {
|
if (shouldHavePostLogin()) {
|
||||||
@ -127,8 +135,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
client.tryToGet(url("/login")) {
|
client.tryToGet(url("/login")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -150,8 +160,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
client.tryToPost(url("/login")) {
|
client.tryToPost(url("/login")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -168,8 +180,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun shouldHaveNewLogout() =
|
private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= VERSION_WHERE_POST_LOGIN_SHOULD_WORK // We are missing 4.1.0
|
||||||
appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
|
|
||||||
|
|
||||||
suspend fun logout(): SuccessResponse =
|
suspend fun logout(): SuccessResponse =
|
||||||
if (shouldHaveNewLogout()) {
|
if (shouldHaveNewLogout()) {
|
||||||
@ -181,8 +192,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
private suspend fun maybeLogoutIfAvailable() =
|
private suspend fun maybeLogoutIfAvailable() =
|
||||||
responseOrSuccessIf404(
|
responseOrSuccessIf404(
|
||||||
client.tryToGet(url("/logout")) {
|
client.tryToGet(url("/logout")) {
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -202,8 +215,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
private suspend fun doLogout() =
|
private suspend fun doLogout() =
|
||||||
maybeResponse(
|
maybeResponse(
|
||||||
client.tryToDelete(url("/api/session/current")) {
|
client.tryToDelete(url("/api/session/current")) {
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -242,8 +257,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("updatedsince", updatedSince)
|
parameter("updatedsince", updatedSince)
|
||||||
parameter("items", items ?: appSettingsService.getItemsNumber())
|
parameter("items", items ?: appSettingsService.getItemsNumber())
|
||||||
parameter("offset", offset)
|
parameter("offset", offset)
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -269,8 +286,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
}
|
}
|
||||||
parameter("type", "all")
|
parameter("type", "all")
|
||||||
parameter("items", 1)
|
parameter("items", 1)
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -294,8 +313,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -319,8 +340,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -344,8 +367,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -369,8 +394,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -394,8 +421,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -419,8 +448,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -440,8 +471,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> =
|
suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> =
|
||||||
bodyOrFailure(
|
bodyOrFailure(
|
||||||
client.tryToGet(url("/api/about")) {
|
client.tryToGet(url("/api/about")) {
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -465,8 +498,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -490,8 +525,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -515,8 +552,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -540,8 +579,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -563,16 +604,18 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
client.tryToSubmitForm(
|
client.tryToSubmitForm(
|
||||||
url = url("/mark"),
|
url = url("/mark"),
|
||||||
formParameters =
|
formParameters =
|
||||||
Parameters.build {
|
Parameters.build {
|
||||||
if (!shouldHavePostLogin()) {
|
if (!shouldHavePostLogin()) {
|
||||||
append("username", appSettingsService.getUserName())
|
append("username", appSettingsService.getUserName())
|
||||||
append("password", appSettingsService.getPassword())
|
append("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
ids.map { append("ids[]", it) }
|
ids.map { append("ids[]", it) }
|
||||||
},
|
},
|
||||||
block = {
|
block = {
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -614,19 +657,21 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
client.tryToSubmitForm(
|
client.tryToSubmitForm(
|
||||||
url = url("/source"),
|
url = url("/source"),
|
||||||
formParameters =
|
formParameters =
|
||||||
Parameters.build {
|
Parameters.build {
|
||||||
if (!shouldHavePostLogin()) {
|
if (!shouldHavePostLogin()) {
|
||||||
append("username", appSettingsService.getUserName())
|
append("username", appSettingsService.getUserName())
|
||||||
append("password", appSettingsService.getPassword())
|
append("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
append("title", title)
|
append("title", title)
|
||||||
append("url", url)
|
append("url", url)
|
||||||
append("spout", spout)
|
append("spout", spout)
|
||||||
append(tagsParamName, tags)
|
append(tagsParamName, tags)
|
||||||
},
|
},
|
||||||
block = {
|
block = {
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -669,19 +714,21 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
client.tryToSubmitForm(
|
client.tryToSubmitForm(
|
||||||
url = url("/source/$id"),
|
url = url("/source/$id"),
|
||||||
formParameters =
|
formParameters =
|
||||||
Parameters.build {
|
Parameters.build {
|
||||||
if (!shouldHavePostLogin()) {
|
if (!shouldHavePostLogin()) {
|
||||||
append("username", appSettingsService.getUserName())
|
append("username", appSettingsService.getUserName())
|
||||||
append("password", appSettingsService.getPassword())
|
append("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
append("title", title)
|
append("title", title)
|
||||||
append("url", url)
|
append("url", url)
|
||||||
append("spout", spout)
|
append("spout", spout)
|
||||||
append(tagsParamName, tags)
|
append(tagsParamName, tags)
|
||||||
},
|
},
|
||||||
block = {
|
block = {
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
@ -705,8 +752,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName()
|
if (appSettingsService
|
||||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
) {
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("detekt:TooManyFunctions")
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.service
|
package bou.amine.apps.readerforselfossv2.service
|
||||||
|
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
|
@ -1,8 +1,22 @@
|
|||||||
|
@file:Suppress("detekt:TooManyFunctions")
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.service
|
package bou.amine.apps.readerforselfossv2.service
|
||||||
|
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
|
|
||||||
class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
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,
|
||||||
|
) {
|
||||||
val settings: Settings =
|
val settings: Settings =
|
||||||
if (acraSenderServiceProcess) {
|
if (acraSenderServiceProcess) {
|
||||||
ACRASettings()
|
ACRASettings()
|
||||||
@ -11,37 +25,37 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Api related
|
// Api related
|
||||||
private var _apiVersion: Int = -1
|
private var apiVersion: Int = -1
|
||||||
private var _publicAccess: Boolean? = null
|
private var publicAccess: Boolean? = null
|
||||||
private var _selfSigned: Boolean? = null
|
private var selfSigned: Boolean? = null
|
||||||
private var _baseUrl: String = ""
|
private var baseUrl: String = ""
|
||||||
private var _userName: String = ""
|
private var userName: String = ""
|
||||||
private var _basicUserName: String = ""
|
private var basicUserName: String = ""
|
||||||
private var _password: String = ""
|
private var password: String = ""
|
||||||
private var _basicPassword: String = ""
|
private var basicPassword: String = ""
|
||||||
|
|
||||||
// User settings related
|
// User settings related
|
||||||
private var _itemsCaching: Boolean? = null
|
private var itemsCaching: Boolean? = null
|
||||||
private var _articleViewer: Boolean? = null
|
private var articleViewer: Boolean? = null
|
||||||
private var _shouldBeCardView: Boolean? = null
|
private var shouldBeCardView: Boolean? = null
|
||||||
private var _displayUnreadCount: Boolean? = null
|
private var displayUnreadCount: Boolean? = null
|
||||||
private var _displayAllCount: Boolean? = null
|
private var displayAllCount: Boolean? = null
|
||||||
private var _fullHeightCards: Boolean? = null
|
private var fullHeightCards: Boolean? = null
|
||||||
private var _updateSources: Boolean? = null
|
private var updateSources: Boolean? = null
|
||||||
private var _periodicRefresh: Boolean? = null
|
private var periodicRefresh: Boolean? = null
|
||||||
private var _refreshWhenChargingOnly: Boolean? = null
|
private var refreshWhenChargingOnly: Boolean? = null
|
||||||
private var _infiniteLoading: Boolean? = null
|
private var infiniteLoading: Boolean? = null
|
||||||
private var _notifyNewItems: Boolean? = null
|
private var notifyNewItems: Boolean? = null
|
||||||
private var _itemsNumber: Int? = null
|
private var itemsNumber: Int? = null
|
||||||
private var _apiTimeout: Long? = null
|
private var apiTimeout: Long? = null
|
||||||
private var _refreshMinutes: Long = 360
|
private var refreshMinutes: Long = DEFAULT_REFRESH_MINUTES
|
||||||
private var _markOnScroll: Boolean? = null
|
private var markOnScroll: Boolean? = null
|
||||||
private var _activeAlignment: Int? = null
|
private var activeAlignment: Int? = null
|
||||||
|
|
||||||
private var _fontSize: Int? = null
|
private var fontSize: Int? = null
|
||||||
private var _staticBar: Boolean? = null
|
private var staticBar: Boolean? = null
|
||||||
private var _font: String = ""
|
private var font: String = ""
|
||||||
private var _theme: Int? = null
|
private var theme: Int? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
refreshApiSettings()
|
refreshApiSettings()
|
||||||
@ -49,11 +63,11 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getApiVersion(): Int {
|
fun getApiVersion(): Int {
|
||||||
if (_apiVersion == -1) {
|
if (apiVersion == -1) {
|
||||||
refreshApiVersion()
|
refreshApiVersion()
|
||||||
return _apiVersion
|
return apiVersion
|
||||||
}
|
}
|
||||||
return _apiVersion
|
return apiVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateApiVersion(apiMajorVersion: Int) {
|
fun updateApiVersion(apiMajorVersion: Int) {
|
||||||
@ -62,14 +76,14 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshApiVersion() {
|
private fun refreshApiVersion() {
|
||||||
_apiVersion = settings.getInt(API_VERSION_MAJOR, -1)
|
apiVersion = settings.getInt(API_VERSION_MAJOR, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPublicAccess(): Boolean {
|
fun getPublicAccess(): Boolean {
|
||||||
if (_publicAccess == null) {
|
if (publicAccess == null) {
|
||||||
refreshPublicAccess()
|
refreshPublicAccess()
|
||||||
}
|
}
|
||||||
return _publicAccess!!
|
return publicAccess!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updatePublicAccess(publicAccess: Boolean) {
|
fun updatePublicAccess(publicAccess: Boolean) {
|
||||||
@ -78,14 +92,14 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshPublicAccess() {
|
private fun refreshPublicAccess() {
|
||||||
_publicAccess = settings.getBoolean(API_PUBLIC_ACCESS, false)
|
publicAccess = settings.getBoolean(API_PUBLIC_ACCESS, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSelfSigned(): Boolean {
|
fun getSelfSigned(): Boolean {
|
||||||
if (_selfSigned == null) {
|
if (selfSigned == null) {
|
||||||
refreshSelfSigned()
|
refreshSelfSigned()
|
||||||
}
|
}
|
||||||
return _selfSigned!!
|
return selfSigned!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSelfSigned(selfSigned: Boolean) {
|
fun updateSelfSigned(selfSigned: Boolean) {
|
||||||
@ -94,312 +108,315 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshSelfSigned() {
|
private fun refreshSelfSigned() {
|
||||||
_selfSigned = settings.getBoolean(API_SELF_SIGNED, false)
|
selfSigned = settings.getBoolean(API_SELF_SIGNED, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBaseUrl(): String {
|
fun getBaseUrl(): String {
|
||||||
if (_baseUrl.isEmpty()) {
|
if (baseUrl.isEmpty()) {
|
||||||
refreshBaseUrl()
|
refreshBaseUrl()
|
||||||
}
|
}
|
||||||
return _baseUrl
|
return baseUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserName(): String {
|
fun getUserName(): String {
|
||||||
if (_userName.isEmpty()) {
|
if (userName.isEmpty()) {
|
||||||
refreshUsername()
|
refreshUsername()
|
||||||
}
|
}
|
||||||
return _userName
|
return userName
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPassword(): String {
|
fun getPassword(): String {
|
||||||
if (_password.isEmpty()) {
|
if (password.isEmpty()) {
|
||||||
refreshPassword()
|
refreshPassword()
|
||||||
}
|
}
|
||||||
return _password
|
return password
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBasicUserName(): String {
|
fun getBasicUserName(): String {
|
||||||
if (_basicUserName.isEmpty()) {
|
if (basicUserName.isEmpty()) {
|
||||||
refreshBasicUsername()
|
refreshBasicUsername()
|
||||||
}
|
}
|
||||||
return _basicUserName
|
return basicUserName
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBasicPassword(): String {
|
fun getBasicPassword(): String {
|
||||||
if (_basicPassword.isEmpty()) {
|
if (basicPassword.isEmpty()) {
|
||||||
refreshBasicPassword()
|
refreshBasicPassword()
|
||||||
}
|
}
|
||||||
return _basicPassword
|
return basicPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getItemsNumber(): Int {
|
fun getItemsNumber(): Int {
|
||||||
if (_itemsNumber == null) {
|
if (itemsNumber == null) {
|
||||||
refreshItemsNumber()
|
refreshItemsNumber()
|
||||||
}
|
}
|
||||||
return _itemsNumber!!
|
return itemsNumber!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:SwallowedException")
|
||||||
private fun refreshItemsNumber() {
|
private fun refreshItemsNumber() {
|
||||||
_itemsNumber =
|
itemsNumber =
|
||||||
try {
|
try {
|
||||||
settings.getString(API_ITEMS_NUMBER, "20").toInt()
|
settings.getString(API_ITEMS_NUMBER, DEFAULT_ITEMS_NUMBER.toString()).toInt()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
settings.remove(API_ITEMS_NUMBER)
|
settings.remove(API_ITEMS_NUMBER)
|
||||||
20
|
DEFAULT_ITEMS_NUMBER
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getApiTimeout(): Long {
|
fun getApiTimeout(): Long {
|
||||||
if (_apiTimeout == null) {
|
if (apiTimeout == null) {
|
||||||
refreshApiTimeout()
|
refreshApiTimeout()
|
||||||
}
|
}
|
||||||
return _apiTimeout!!
|
return apiTimeout!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:MagicNumber")
|
||||||
private fun secToMs(n: Long) = n * 1000
|
private fun secToMs(n: Long) = n * 1000
|
||||||
|
|
||||||
|
@Suppress("detekt:SwallowedException")
|
||||||
private fun refreshApiTimeout() {
|
private fun refreshApiTimeout() {
|
||||||
_apiTimeout =
|
apiTimeout =
|
||||||
secToMs(
|
secToMs(
|
||||||
try {
|
try {
|
||||||
val settingsTimeout = settings.getString(API_TIMEOUT, "60")
|
val settingsTimeout = settings.getString(API_TIMEOUT, DEFAULT_API_TIMEOUT.toString())
|
||||||
if (settingsTimeout.toLong() > 0) {
|
if (settingsTimeout.toLong() > 0) {
|
||||||
settingsTimeout.toLong()
|
settingsTimeout.toLong()
|
||||||
} else {
|
} else {
|
||||||
settings.remove(API_TIMEOUT)
|
settings.remove(API_TIMEOUT)
|
||||||
60
|
DEFAULT_API_TIMEOUT
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
settings.remove(API_TIMEOUT)
|
settings.remove(API_TIMEOUT)
|
||||||
60
|
DEFAULT_API_TIMEOUT
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshBaseUrl() {
|
private fun refreshBaseUrl() {
|
||||||
_baseUrl = settings.getString(BASE_URL, "")
|
baseUrl = settings.getString(BASE_URL, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshUsername() {
|
private fun refreshUsername() {
|
||||||
_userName = settings.getString(LOGIN, "")
|
userName = settings.getString(LOGIN, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshPassword() {
|
private fun refreshPassword() {
|
||||||
_password = settings.getString(PASSWORD, "")
|
password = settings.getString(PASSWORD, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshBasicUsername() {
|
private fun refreshBasicUsername() {
|
||||||
_basicUserName = settings.getString(BASIC_LOGIN, "")
|
basicUserName = settings.getString(BASIC_LOGIN, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshBasicPassword() {
|
private fun refreshBasicPassword() {
|
||||||
_basicPassword = settings.getString(BASIC_PASSWORD, "")
|
basicPassword = settings.getString(BASIC_PASSWORD, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshArticleViewerEnabled() {
|
private fun refreshArticleViewerEnabled() {
|
||||||
_articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true)
|
articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isArticleViewerEnabled(): Boolean {
|
fun isArticleViewerEnabled(): Boolean {
|
||||||
if (_articleViewer != null) {
|
if (articleViewer != null) {
|
||||||
refreshArticleViewerEnabled()
|
refreshArticleViewerEnabled()
|
||||||
}
|
}
|
||||||
return _articleViewer == true
|
return articleViewer == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshShouldBeCardViewEnabled() {
|
private fun refreshShouldBeCardViewEnabled() {
|
||||||
_shouldBeCardView = settings.getBoolean(CARD_VIEW_ACTIVE, false)
|
shouldBeCardView = settings.getBoolean(CARD_VIEW_ACTIVE, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isCardViewEnabled(): Boolean {
|
fun isCardViewEnabled(): Boolean {
|
||||||
if (_shouldBeCardView != null) {
|
if (shouldBeCardView != null) {
|
||||||
refreshShouldBeCardViewEnabled()
|
refreshShouldBeCardViewEnabled()
|
||||||
}
|
}
|
||||||
return _shouldBeCardView == true
|
return shouldBeCardView == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshDisplayUnreadCountEnabled() {
|
private fun refreshDisplayUnreadCountEnabled() {
|
||||||
_displayUnreadCount = settings.getBoolean(DISPLAY_UNREAD_COUNT, true)
|
displayUnreadCount = settings.getBoolean(DISPLAY_UNREAD_COUNT, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isDisplayUnreadCountEnabled(): Boolean {
|
fun isDisplayUnreadCountEnabled(): Boolean {
|
||||||
if (_displayUnreadCount != null) {
|
if (displayUnreadCount != null) {
|
||||||
refreshDisplayUnreadCountEnabled()
|
refreshDisplayUnreadCountEnabled()
|
||||||
}
|
}
|
||||||
return _displayUnreadCount == true
|
return displayUnreadCount == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshDisplayAllCountEnabled() {
|
private fun refreshDisplayAllCountEnabled() {
|
||||||
_displayAllCount = settings.getBoolean(DISPLAY_OTHER_COUNT, false)
|
displayAllCount = settings.getBoolean(DISPLAY_OTHER_COUNT, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isDisplayAllCountEnabled(): Boolean {
|
fun isDisplayAllCountEnabled(): Boolean {
|
||||||
if (_displayAllCount != null) {
|
if (displayAllCount != null) {
|
||||||
refreshDisplayAllCountEnabled()
|
refreshDisplayAllCountEnabled()
|
||||||
}
|
}
|
||||||
return _displayAllCount == true
|
return displayAllCount == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshFullHeightCardsEnabled() {
|
private fun refreshFullHeightCardsEnabled() {
|
||||||
_fullHeightCards = settings.getBoolean(FULL_HEIGHT_CARDS, false)
|
fullHeightCards = settings.getBoolean(FULL_HEIGHT_CARDS, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isFullHeightCardsEnabled(): Boolean {
|
fun isFullHeightCardsEnabled(): Boolean {
|
||||||
if (_fullHeightCards != null) {
|
if (fullHeightCards != null) {
|
||||||
refreshFullHeightCardsEnabled()
|
refreshFullHeightCardsEnabled()
|
||||||
}
|
}
|
||||||
return _fullHeightCards == true
|
return fullHeightCards == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshUpdateSourcesEnabled() {
|
private fun refreshUpdateSourcesEnabled() {
|
||||||
_updateSources = settings.getBoolean(UPDATE_SOURCES, true)
|
updateSources = settings.getBoolean(UPDATE_SOURCES, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isUpdateSourcesEnabled(): Boolean {
|
fun isUpdateSourcesEnabled(): Boolean {
|
||||||
if (_updateSources != null) {
|
if (updateSources != null) {
|
||||||
refreshUpdateSourcesEnabled()
|
refreshUpdateSourcesEnabled()
|
||||||
}
|
}
|
||||||
return _updateSources == true
|
return updateSources == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshPeriodicRefreshEnabled() {
|
private fun refreshPeriodicRefreshEnabled() {
|
||||||
_periodicRefresh = settings.getBoolean(PERIODIC_REFRESH, false)
|
periodicRefresh = settings.getBoolean(PERIODIC_REFRESH, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPeriodicRefreshEnabled(): Boolean {
|
fun isPeriodicRefreshEnabled(): Boolean {
|
||||||
if (_periodicRefresh != null) {
|
if (periodicRefresh != null) {
|
||||||
refreshPeriodicRefreshEnabled()
|
refreshPeriodicRefreshEnabled()
|
||||||
}
|
}
|
||||||
return _periodicRefresh == true
|
return periodicRefresh == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshRefreshWhenChargingOnlyEnabled() {
|
private fun refreshRefreshWhenChargingOnlyEnabled() {
|
||||||
_refreshWhenChargingOnly = settings.getBoolean(REFRESH_WHEN_CHARGING, false)
|
refreshWhenChargingOnly = settings.getBoolean(REFRESH_WHEN_CHARGING, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isRefreshWhenChargingOnlyEnabled(): Boolean {
|
fun isRefreshWhenChargingOnlyEnabled(): Boolean {
|
||||||
if (_refreshWhenChargingOnly != null) {
|
if (refreshWhenChargingOnly != null) {
|
||||||
refreshRefreshWhenChargingOnlyEnabled()
|
refreshRefreshWhenChargingOnlyEnabled()
|
||||||
}
|
}
|
||||||
return _refreshWhenChargingOnly == true
|
return refreshWhenChargingOnly == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshRefreshMinutes() {
|
private fun refreshRefreshMinutes() {
|
||||||
_refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, "360").toLong()
|
refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, DEFAULT_REFRESH_MINUTES.toString()).toLong()
|
||||||
if (_refreshMinutes <= 15) {
|
if (refreshMinutes <= MIN_REFRESH_MINUTES) {
|
||||||
_refreshMinutes = 15
|
refreshMinutes = MIN_REFRESH_MINUTES
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRefreshMinutes(): Long {
|
fun getRefreshMinutes(): Long {
|
||||||
if (_refreshMinutes != 360L) {
|
if (refreshMinutes != DEFAULT_REFRESH_MINUTES) {
|
||||||
refreshRefreshMinutes()
|
refreshRefreshMinutes()
|
||||||
}
|
}
|
||||||
return _refreshMinutes
|
return refreshMinutes
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshInfiniteLoadingEnabled() {
|
private fun refreshInfiniteLoadingEnabled() {
|
||||||
_infiniteLoading = settings.getBoolean(INFINITE_LOADING, false)
|
infiniteLoading = settings.getBoolean(INFINITE_LOADING, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isInfiniteLoadingEnabled(): Boolean {
|
fun isInfiniteLoadingEnabled(): Boolean {
|
||||||
if (_infiniteLoading != null) {
|
if (infiniteLoading != null) {
|
||||||
refreshInfiniteLoadingEnabled()
|
refreshInfiniteLoadingEnabled()
|
||||||
}
|
}
|
||||||
return _infiniteLoading == true
|
return infiniteLoading == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshItemCachingEnabled() {
|
private fun refreshItemCachingEnabled() {
|
||||||
_itemsCaching = settings.getBoolean(ITEMS_CACHING, false)
|
itemsCaching = settings.getBoolean(ITEMS_CACHING, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isItemCachingEnabled(): Boolean {
|
fun isItemCachingEnabled(): Boolean {
|
||||||
if (_itemsCaching != null) {
|
if (itemsCaching != null) {
|
||||||
refreshItemCachingEnabled()
|
refreshItemCachingEnabled()
|
||||||
}
|
}
|
||||||
return _itemsCaching == true
|
return itemsCaching == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshNotifyNewItemsEnabled() {
|
private fun refreshNotifyNewItemsEnabled() {
|
||||||
_notifyNewItems = settings.getBoolean(NOTIFY_NEW_ITEMS, false)
|
notifyNewItems = settings.getBoolean(NOTIFY_NEW_ITEMS, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isNotifyNewItemsEnabled(): Boolean {
|
fun isNotifyNewItemsEnabled(): Boolean {
|
||||||
if (_notifyNewItems != null) {
|
if (notifyNewItems != null) {
|
||||||
refreshNotifyNewItemsEnabled()
|
refreshNotifyNewItemsEnabled()
|
||||||
}
|
}
|
||||||
return _notifyNewItems == true
|
return notifyNewItems == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshMarkOnScrollEnabled() {
|
private fun refreshMarkOnScrollEnabled() {
|
||||||
_markOnScroll = settings.getBoolean(MARK_ON_SCROLL, false)
|
markOnScroll = settings.getBoolean(MARK_ON_SCROLL, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isMarkOnScrollEnabled(): Boolean {
|
fun isMarkOnScrollEnabled(): Boolean {
|
||||||
if (_markOnScroll != null) {
|
if (markOnScroll != null) {
|
||||||
refreshMarkOnScrollEnabled()
|
refreshMarkOnScrollEnabled()
|
||||||
}
|
}
|
||||||
return _markOnScroll == true
|
return markOnScroll == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshActiveAllignment() {
|
private fun refreshActiveAllignment() {
|
||||||
_activeAlignment = settings.getInt(TEXT_ALIGN, JUSTIFY)
|
activeAlignment = settings.getInt(TEXT_ALIGN, JUSTIFY)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getActiveAllignment(): Int {
|
fun getActiveAllignment(): Int {
|
||||||
if (_activeAlignment != null) {
|
if (activeAlignment != null) {
|
||||||
refreshActiveAllignment()
|
refreshActiveAllignment()
|
||||||
}
|
}
|
||||||
return _activeAlignment ?: JUSTIFY
|
return activeAlignment ?: JUSTIFY
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeAllignment(allignment: Int) {
|
fun changeAllignment(allignment: Int) {
|
||||||
settings.putInt(TEXT_ALIGN, allignment)
|
settings.putInt(TEXT_ALIGN, allignment)
|
||||||
_activeAlignment = allignment
|
activeAlignment = allignment
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshFontSize() {
|
private fun refreshFontSize() {
|
||||||
_fontSize = settings.getString(READER_FONT_SIZE, "16").toInt()
|
fontSize = settings.getString(READER_FONT_SIZE, "16").toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFontSize(): Int {
|
fun getFontSize(): Int {
|
||||||
if (_fontSize != null) {
|
if (fontSize != null) {
|
||||||
refreshFontSize()
|
refreshFontSize()
|
||||||
}
|
}
|
||||||
return _fontSize ?: 16
|
return fontSize ?: DEFAULT_FONT_SIZE
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshStaticBarEnabled() {
|
private fun refreshStaticBarEnabled() {
|
||||||
_staticBar = settings.getBoolean(READER_STATIC_BAR, false)
|
staticBar = settings.getBoolean(READER_STATIC_BAR, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isStaticBarEnabled(): Boolean {
|
fun isStaticBarEnabled(): Boolean {
|
||||||
if (_staticBar != null) {
|
if (staticBar != null) {
|
||||||
refreshStaticBarEnabled()
|
refreshStaticBarEnabled()
|
||||||
}
|
}
|
||||||
return _staticBar == true
|
return staticBar == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshFont() {
|
private fun refreshFont() {
|
||||||
_font = settings.getString(READER_FONT, "")
|
font = settings.getString(READER_FONT, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFont(): String {
|
fun getFont(): String {
|
||||||
if (_font.isEmpty()) {
|
if (font.isEmpty()) {
|
||||||
refreshFont()
|
refreshFont()
|
||||||
}
|
}
|
||||||
return _font
|
return font
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshCurrentTheme() {
|
private fun refreshCurrentTheme() {
|
||||||
_theme = settings.getString(CURRENT_THEME, "-1").toInt()
|
theme = settings.getString(CURRENT_THEME, "-1").toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCurrentTheme(): Int {
|
fun getCurrentTheme(): Int {
|
||||||
if (_theme == null) {
|
if (theme == null) {
|
||||||
refreshCurrentTheme()
|
refreshCurrentTheme()
|
||||||
}
|
}
|
||||||
return _theme ?: -1
|
return theme ?: -1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshApiSettings() {
|
fun refreshApiSettings() {
|
||||||
@ -478,15 +495,15 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val translationUrl = "https://crwd.in/readerforselfoss"
|
const val TRANSLATION_URL = "https://crwd.in/readerforselfoss"
|
||||||
|
|
||||||
const val sourceUrl = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform"
|
const val SOURCE_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform"
|
||||||
|
|
||||||
const val trackerUrl = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
const val BUG_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
||||||
|
|
||||||
const val syncChannelId = "sync-channel-id"
|
const val SYNC_CHANNEL_ID = "sync-channel-id"
|
||||||
|
|
||||||
const val newItemsChannelId = "new-items-channel-id"
|
const val NEW_ITEMS_CHANNEL = "new-items-channel-id"
|
||||||
|
|
||||||
const val JUSTIFY = 1
|
const val JUSTIFY = 1
|
||||||
|
|
||||||
|
@ -4,6 +4,12 @@ import kotlinx.datetime.LocalDateTime
|
|||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.datetime.toInstant
|
import kotlinx.datetime.toInstant
|
||||||
|
|
||||||
|
class DateParseException(
|
||||||
|
message: String,
|
||||||
|
e: Throwable? = null,
|
||||||
|
) : Throwable(message, e)
|
||||||
|
|
||||||
|
@Suppress("detekt:ThrowsCount")
|
||||||
fun String.toParsedDate(): Long {
|
fun String.toParsedDate(): Long {
|
||||||
// Possible formats are
|
// Possible formats are
|
||||||
// yyyy-mm-dd hh:mm:ss format
|
// yyyy-mm-dd hh:mm:ss format
|
||||||
@ -17,17 +23,22 @@ fun String.toParsedDate(): Long {
|
|||||||
if (this.matches(oldVersionFormat)) {
|
if (this.matches(oldVersionFormat)) {
|
||||||
this.replace(" ", "T")
|
this.replace(" ", "T")
|
||||||
} else if (this.matches(newVersionFormat)) {
|
} else if (this.matches(newVersionFormat)) {
|
||||||
newVersionFormat.find(this)?.groups?.get(1)?.value ?: throw Exception("Couldn't parse $this")
|
newVersionFormat
|
||||||
|
.find(this)
|
||||||
|
?.groups
|
||||||
|
?.get(1)
|
||||||
|
?.value ?: throw DateParseException("Couldn't parse $this")
|
||||||
} else {
|
} else {
|
||||||
throw Exception("Unrecognized format for $this")
|
throw DateParseException("Unrecognized format for $this")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw Exception("parseDate failed for $this", e)
|
throw DateParseException("parseDate failed for $this", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
|
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
||||||
expect class DateUtils() {
|
expect class DateUtils() {
|
||||||
companion object {
|
companion object {
|
||||||
fun parseRelativeDate(dateString: String): String
|
fun parseRelativeDate(dateString: String): String
|
||||||
|
@ -17,7 +17,7 @@ fun SOURCE.toView(): SelfossModel.SourceDetail =
|
|||||||
this.id.toInt(),
|
this.id.toInt(),
|
||||||
this.title,
|
this.title,
|
||||||
null,
|
null,
|
||||||
this.tags?.split(","),
|
this.tags.split(","),
|
||||||
this.spout,
|
this.spout,
|
||||||
this.error,
|
this.error,
|
||||||
this.icon,
|
this.icon,
|
||||||
@ -74,6 +74,7 @@ fun SelfossModel.Item.toEntity(): ITEM =
|
|||||||
this.author,
|
this.author,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Suppress("detekt:MagicNumber")
|
||||||
fun SelfossModel.Tag.getColorHexCode(): String =
|
fun SelfossModel.Tag.getColorHexCode(): String =
|
||||||
if (this.color.length == 4) { // #000
|
if (this.color.length == 4) { // #000
|
||||||
val char1 = this.color.get(1)
|
val char1 = this.color.get(1)
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
enum class ItemType(val position: Int, val type: String) {
|
@Suppress("detekt:MagicNumber")
|
||||||
|
enum class ItemType(
|
||||||
|
val position: Int,
|
||||||
|
val type: String,
|
||||||
|
) {
|
||||||
UNREAD(1, "unread"),
|
UNREAD(1, "unread"),
|
||||||
ALL(2, "all"),
|
ALL(2, "all"),
|
||||||
STARRED(3, "starred"),
|
STARRED(3, "starred"),
|
@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.utils
|
|||||||
|
|
||||||
fun String?.isEmptyOrNullOrNullString(): Boolean = this == null || this == "null" || this.isEmpty()
|
fun String?.isEmptyOrNullOrNullString(): Boolean = this == null || this == "null" || this.isEmpty()
|
||||||
|
|
||||||
|
@Suppress("detekt:MagicNumber")
|
||||||
fun String.longHash(): Long {
|
fun String.longHash(): Long {
|
||||||
var h = 98764321261L
|
var h = 98764321261L
|
||||||
val l = this.length
|
val l = this.length
|
||||||
|
@ -8,27 +8,29 @@ import kotlin.test.Test
|
|||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class DatesTest {
|
class DatesTest {
|
||||||
private val newVersionDateVariant = "2022-12-24T17:00:08+00"
|
private val newVersionDateVariant = "2022-12-24T17:00:08+00"
|
||||||
private val newVersionDate = "2013-04-07T13:43:00+01:00"
|
private val newVersionDate = "2013-04-07T13:43:00+01:00"
|
||||||
private val newVersionDate2 = "2013-04-07T13:43:00-01:00"
|
private val newVersionDate2 = "2013-04-07T13:43:00-01:00"
|
||||||
private val oldVersionDate = "2013-05-07 13:46:00"
|
private val oldVersionDate = "2013-05-07 13:46:00"
|
||||||
private val oldVersionDateVariant = "2021-03-21 10:32:00.000000"
|
private val oldVersionDateVariant = "2021-03-21 10:32:00.000000"
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun new_version_date_should_be_parsed() {
|
fun new_version_date_should_be_parsed() {
|
||||||
val date = newVersionDate.toParsedDate()
|
val date = newVersionDate.toParsedDate()
|
||||||
val expected =
|
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()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun new_version_date2_should_be_parsed() {
|
fun new_version_date2_should_be_parsed() {
|
||||||
val date = newVersionDate2.toParsedDate()
|
val date = newVersionDate2.toParsedDate()
|
||||||
val expected =
|
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()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
||||||
@ -38,7 +40,8 @@ class DatesTest {
|
|||||||
fun old_version_date_should_be_parsed() {
|
fun old_version_date_should_be_parsed() {
|
||||||
val date = oldVersionDate.toParsedDate()
|
val date = oldVersionDate.toParsedDate()
|
||||||
val expected =
|
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()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
||||||
@ -48,7 +51,8 @@ class DatesTest {
|
|||||||
fun old_version_variant_date_should_be_parsed() {
|
fun old_version_variant_date_should_be_parsed() {
|
||||||
val date = oldVersionDateVariant.toParsedDate()
|
val date = oldVersionDateVariant.toParsedDate()
|
||||||
val expected =
|
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()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
||||||
@ -58,7 +62,8 @@ class DatesTest {
|
|||||||
fun new_version_variant_date_should_be_parsed() {
|
fun new_version_variant_date_should_be_parsed() {
|
||||||
val date = newVersionDateVariant.toParsedDate()
|
val date = newVersionDateVariant.toParsedDate()
|
||||||
val expected =
|
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()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
@ -4,7 +4,5 @@ import app.cash.sqldelight.db.SqlDriver
|
|||||||
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
||||||
|
|
||||||
actual class DriverFactory {
|
actual class DriverFactory {
|
||||||
actual fun createDriver(): SqlDriver {
|
actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||||
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.rest
|
||||||
|
|
||||||
|
import io.ktor.client.engine.cio.CIOEngineConfig
|
||||||
|
|
||||||
|
@Suppress("detekt:EmptyFunctionBlock")
|
||||||
|
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
||||||
|
}
|
@ -1,6 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.rest
|
|
||||||
|
|
||||||
import io.ktor.client.engine.cio.CIOEngineConfig
|
|
||||||
|
|
||||||
actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) {
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
|
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
||||||
actual class DateUtils {
|
actual class DateUtils {
|
||||||
actual companion object {
|
actual companion object {
|
||||||
actual fun parseRelativeDate(dateString: String): String {
|
actual fun parseRelativeDate(dateString: String): String {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
|
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
||||||
actual class DateUtils actual constructor() {
|
actual class DateUtils actual constructor() {
|
||||||
actual companion object {
|
actual companion object {
|
||||||
actual fun parseRelativeDate(dateString: String): String {
|
actual fun parseRelativeDate(dateString: String): String {
|
@ -4,7 +4,5 @@ import app.cash.sqldelight.db.SqlDriver
|
|||||||
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
||||||
|
|
||||||
actual class DriverFactory {
|
actual class DriverFactory {
|
||||||
actual fun createDriver(): SqlDriver {
|
actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||||
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.rest
|
||||||
|
|
||||||
|
import io.ktor.client.engine.cio.CIOEngineConfig
|
||||||
|
|
||||||
|
@Suppress("detekt:EmptyFunctionBlock")
|
||||||
|
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
||||||
|
}
|
@ -1,6 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.rest
|
|
||||||
|
|
||||||
import io.ktor.client.engine.cio.CIOEngineConfig
|
|
||||||
|
|
||||||
actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) {
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
|
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
||||||
actual class DateUtils {
|
actual class DateUtils {
|
||||||
actual companion object {
|
actual companion object {
|
||||||
actual fun parseRelativeDate(dateString: String): String {
|
actual fun parseRelativeDate(dateString: String): String {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user