chore: code style fixes for ktlint
Some checks failed
Check PR code / Lint (pull_request) Failing after 2m19s
Check PR code / build (pull_request) Has been skipped

This commit is contained in:
Amine Bouabdallaoui 2025-01-11 15:23:02 +01:00
parent 5035392aff
commit 4fbebf2954
67 changed files with 1062 additions and 877 deletions

36
.editorconfig Normal file
View 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

View File

@ -20,7 +20,7 @@ jobs:
- 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.1/detekt-cli-1.23.1.zip && unzip detekt-cli-1.23.1.zip
- name: Linting... - name: Linting...
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build' || 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.1/bin/detekt-cli --all-rules --excludes '**/shared/build/**/*.kt' || true
build: build:

View File

@ -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())

View File

@ -25,8 +25,9 @@ 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) {
@ -48,11 +49,11 @@ fun withError(@StringRes id: Int): TypeSafeMatcher<View?> {
} }
} }
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")
} }
@ -68,43 +69,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>(),
) )
} }

View File

@ -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())))
} }
} }

View File

@ -23,7 +23,6 @@ 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)
@ -37,26 +36,32 @@ class LoginActivityTest {
@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()),
) )
} }

View File

@ -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,7 +36,7 @@ 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())
@ -48,68 +46,75 @@ class SettingsActivityGeneralTest {
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()),
) ),
),
) )
} }
@ -120,25 +125,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 +162,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())))

View File

@ -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,7 +36,7 @@ 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())
@ -49,58 +47,63 @@ class SettingsActivityOfflineTest {
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(),
) ),
),
) )
} }
@ -111,50 +114,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())

View File

@ -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())

View File

@ -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())

View File

@ -41,11 +41,10 @@ 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,
) )
} }
@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 +53,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 +73,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))

View File

@ -49,7 +49,10 @@ 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 { 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,7 +174,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
getElementsAccordingToTab() getElementsAccordingToTab()
} }
} else { } else {
Toast.makeText( Toast
.makeText(
this@HomeActivity, this@HomeActivity,
"Found null when swiping at positon $position.", "Found null when swiping at positon $position.",
Toast.LENGTH_LONG, Toast.LENGTH_LONG,
@ -200,15 +204,18 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
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 +243,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,
@ -425,17 +430,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
.findLastCompletelyVisibleItemPositions(
null, null,
).last() ).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()) {
@ -577,7 +582,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() }
@ -589,7 +595,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
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,14 +612,15 @@ 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
.makeText(
this@HomeActivity, this@HomeActivity,
R.string.refresh_success_response, R.string.refresh_success_response,
Toast.LENGTH_LONG, Toast.LENGTH_LONG,
) ).show()
.show()
} else { } else {
Toast.makeText( Toast
.makeText(
this@HomeActivity, this@HomeActivity,
R.string.refresh_failer_message, R.string.refresh_failer_message,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,
@ -633,7 +640,8 @@ 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
.makeText(
this@HomeActivity, this@HomeActivity,
R.string.all_posts_read, R.string.all_posts_read,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,
@ -642,7 +650,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
getElementsAccordingToTab() getElementsAccordingToTab()
} else { } else {
Toast.makeText( Toast
.makeText(
this@HomeActivity, this@HomeActivity,
R.string.all_posts_not_read, R.string.all_posts_not_read,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,
@ -651,7 +660,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
handleListResult() handleListResult()
binding.swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
CountingIdlingResourceSingleton.decrement() CountingIdlingResourceSingleton.decrement()
} }
} }
} }
@ -661,7 +669,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 +710,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,18 +720,18 @@ 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
.getInstance(
baseContext, baseContext,
).enqueueUniquePeriodicWork( ).enqueueUniquePeriodicWork(
"selfoss-loading", "selfoss-loading",
ExistingPeriodicWorkPolicy.KEEP, ExistingPeriodicWorkPolicy.KEEP,
backgroundWork backgroundWork,
) )
} }
} }

View File

@ -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])

View File

@ -30,7 +30,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 LoginActivity : AppCompatActivity(), DIAware { class LoginActivity :
AppCompatActivity(),
DIAware {
private var inValidCount: Int = 0 private var inValidCount: Int = 0
private var isWithLogin = false private var isWithLogin = false
@ -108,7 +110,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 +134,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,7 +162,8 @@ 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
.makeText(
applicationContext, applicationContext,
R.string.application_selfoss_only, R.string.application_selfoss_only,
Toast.LENGTH_LONG, Toast.LENGTH_LONG,
@ -270,7 +282,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 +292,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
} }

View File

@ -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,7 +36,9 @@ 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)
@ -89,7 +91,8 @@ class MyApp : MultiDexApplication(), DIAware {
R.string.network_connectivity_lost R.string.network_connectivity_lost
} }
Toast.makeText( Toast
.makeText(
applicationContext, applicationContext,
toastMessage, toastMessage,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,
@ -151,13 +154,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,
) )

View File

@ -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
@ -99,19 +101,19 @@ 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 = override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position])
ArticleFragment.newInstance(allItems[position])
} }
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
@ -130,7 +132,6 @@ class ReaderActivity : AppCompatActivity(), DIAware {
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

View File

@ -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,7 +70,8 @@ class SourcesActivity : AppCompatActivity(), DIAware {
binding.recyclerView.adapter = mAdapter binding.recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged() mAdapter.notifyDataSetChanged()
} else { } else {
Toast.makeText( Toast
.makeText(
this@SourcesActivity, this@SourcesActivity,
R.string.cant_get_sources, R.string.cant_get_sources,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,

View File

@ -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
@ -105,7 +107,8 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
} }
fun handleSpoutFailure(networkIssue: Boolean = false) { fun handleSpoutFailure(networkIssue: Boolean = false) {
Toast.makeText( Toast
.makeText(
this@UpsertSourceActivity, this@UpsertSourceActivity,
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts, if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,
@ -192,7 +195,8 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
if (successfullyAddedSource) { if (successfullyAddedSource) {
finish() finish()
} else { } else {
Toast.makeText( Toast
.makeText(
this@UpsertSourceActivity, this@UpsertSourceActivity,
R.string.cant_create_source, R.string.cant_create_source,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,

View File

@ -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,7 +99,8 @@ 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 =
try {
itm.sourceAuthorAndDate() itm.sourceAuthorAndDate()
} catch (e: Exception) { } catch (e: Exception) {
e.sendSilentlyWithAcraWithName("ItemCardAdapter parse date") e.sendSilentlyWithAcraWithName("ItemCardAdapter parse date")
@ -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)
} }

View File

@ -53,7 +53,8 @@ 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 =
try {
itm.sourceAuthorAndDate() itm.sourceAuthorAndDate()
} catch (e: Exception) { } catch (e: Exception) {
e.sendSilentlyWithAcraWithName("ItemListAdapter parse date") e.sendSilentlyWithAcraWithName("ItemListAdapter parse date")
@ -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)
} }

View File

@ -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(

View File

@ -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,7 +62,8 @@ class SourcesListAdapter(
notifyItemRemoved(position) notifyItemRemoved(position)
notifyItemRangeChanged(position, itemCount) notifyItemRangeChanged(position, itemCount)
} else { } else {
Toast.makeText( Toast
.makeText(
app, app,
R.string.can_delete_source, R.string.can_delete_source,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,
@ -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)
} }

View File

@ -23,11 +23,13 @@ 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) : class LoadingWorker(
Worker(context, params), 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 +42,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,19 +90,18 @@ 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
.Builder(
applicationContext, applicationContext,
AppSettingsService.newItemsChannelId, 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)

View File

@ -66,7 +66,9 @@ import java.util.concurrent.ExecutionException
private const val IMAGE_JPG = "image/jpg" private const val IMAGE_JPG = "image/jpg"
class ArticleFragment : Fragment(), DIAware { class ArticleFragment :
Fragment(),
DIAware {
private var fontSize: Int = 16 private var fontSize: Int = 16
private lateinit var item: SelfossModel.Item private lateinit var item: SelfossModel.Item
private lateinit var url: String private lateinit var url: String
@ -115,7 +117,8 @@ 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 =
try {
item.sourceAuthorAndDate() item.sourceAuthorAndDate()
} catch (e: Exception) { } catch (e: Exception) {
e.sendSilentlyWithAcraWithName("Article Fragment parse date") e.sendSilentlyWithAcraWithName("Article Fragment parse date")
@ -166,7 +169,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(
@ -174,8 +178,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")
@ -234,7 +237,8 @@ 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
.makeText(
requireContext(), requireContext(),
R.string.marked_as_read, R.string.marked_as_read,
Toast.LENGTH_LONG, Toast.LENGTH_LONG,
@ -244,7 +248,8 @@ class ArticleFragment : Fragment(), DIAware {
repository.unmarkAsRead(this@ArticleFragment.item) repository.unmarkAsRead(this@ArticleFragment.item)
} }
this@ArticleFragment.item.unread = true this@ArticleFragment.item.unread = true
Toast.makeText( Toast
.makeText(
context, context,
R.string.marked_as_unread, R.string.marked_as_unread,
Toast.LENGTH_LONG, Toast.LENGTH_LONG,
@ -321,8 +326,7 @@ class ArticleFragment : Fragment(), DIAware {
.asBitmap() .asBitmap()
.load( .load(
lead_image_url, lead_image_url,
) ).apply(RequestOptions.fitCenterTransform())
.apply(RequestOptions.fitCenterTransform())
.into(binding.imageView) .into(binding.imageView)
} else { } else {
binding.imageView.visibility = View.GONE binding.imageView.visibility = View.GONE
@ -336,14 +340,16 @@ 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
} }
}
@Deprecated("Deprecated in Java") @Deprecated("Deprecated in Java")
override fun shouldInterceptRequest( override fun shouldInterceptRequest(
@ -352,12 +358,18 @@ class ArticleFragment : Fragment(), DIAware {
): WebResourceResponse? { ): WebResourceResponse? {
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL) val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
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( return WebResourceResponse(
IMAGE_JPG, IMAGE_JPG,
@ -370,7 +382,12 @@ class ArticleFragment : Fragment(), DIAware {
} 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( return WebResourceResponse(
IMAGE_JPG, IMAGE_JPG,
@ -383,7 +400,12 @@ class ArticleFragment : Fragment(), DIAware {
} 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( return WebResourceResponse(
IMAGE_JPG, IMAGE_JPG,
@ -422,7 +444,6 @@ class ArticleFragment : Fragment(), DIAware {
context.theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true) context.theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
context.theme.resolveAttribute(R.attr.colorSurface, colorSurface, true) context.theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
e.sendSilentlyWithAcraWithName("Context issue when setting attributes, but context wasn't null before") e.sendSilentlyWithAcraWithName("Context issue when setting attributes, but context wasn't null before")
} }
@ -450,15 +471,13 @@ 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 -> binding.webcontent.setOnTouchListener { _, event ->
gestureDetector.onTouchEvent( gestureDetector.onTouchEvent(
event event,
) )
} }
@ -597,7 +616,8 @@ 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.IMAGE_TYPE ||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
) )

View File

@ -33,7 +33,9 @@ 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 { 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 +82,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) {

View File

@ -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)

View File

@ -17,9 +17,12 @@ fun SelfossModel.Item.preloadImages(context: Context): Boolean {
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) {

View File

@ -64,15 +64,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,
@ -81,7 +80,8 @@ 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
.instantiate(
classLoader, classLoader,
pref.fragment.toString(), pref.fragment.toString(),
).apply { ).apply {
@ -89,7 +89,8 @@ class SettingsActivity :
setTargetFragment(caller, 0) 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()
@ -108,7 +109,7 @@ class SettingsActivity :
preferenceManager.findPreference<Preference>(CURRENT_THEME)?.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
} }
@ -144,10 +145,11 @@ class SettingsActivity :
val input: Int = (dest.toString() + source.toString()).toInt() val input: Int = (dest.toString() + source.toString()).toInt()
if (input in 1..200) return@InputFilter null if (input in 1..200) return@InputFilter null
} catch (nfe: NumberFormatException) { } catch (nfe: NumberFormatException) {
Toast.makeText( Toast
.makeText(
activity, activity,
R.string.items_number_should_be_number, R.string.items_number_should_be_number,
Toast.LENGTH_LONG Toast.LENGTH_LONG,
).show() ).show()
} }
"" ""
@ -234,19 +236,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
} }
} }

View File

@ -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

View File

@ -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

View File

@ -16,7 +16,8 @@ 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
.createChooser(
sendIntent, sendIntent,
getString(R.string.share), getString(R.string.share),
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), ).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),

View File

@ -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]
}
} }

View File

@ -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,7 +25,8 @@ fun Context.openItemUrl(
app: Activity, app: Activity,
) { ) {
if (!linkDecoded.isUrlValid()) { if (!linkDecoded.isUrlValid()) {
Toast.makeText( Toast
.makeText(
this, this,
this.getString(R.string.cant_open_invalid_url), this.getString(R.string.cant_open_invalid_url),
Toast.LENGTH_LONG, Toast.LENGTH_LONG,
@ -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)
@ -80,7 +78,6 @@ fun Context.mayBeStartActivity(intent: 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 {

View File

@ -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"
}
} }

View File

@ -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,7 +26,8 @@ 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)
} }

View File

@ -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,8 +21,7 @@ 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
} }

View File

@ -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)
} }

View File

@ -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 ->

View File

@ -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()
}
} }

View File

@ -42,14 +42,14 @@ private const val FEED_URL = "https://test.com/feed"
private const val TAGS = "Test, New" private const val TAGS = "Test, New"
private val NUMBER_ARTICLES = 100
private val NUMBER_UNREAD = 50
private 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(
@ -77,10 +77,11 @@ class RepositoryTest {
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
@ -135,10 +136,11 @@ class RepositoryTest {
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 ""
@ -155,10 +157,11 @@ class RepositoryTest {
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"
@ -175,10 +178,11 @@ class RepositoryTest {
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 ""
@ -195,10 +199,11 @@ class RepositoryTest {
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 ""

View File

@ -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"

View File

@ -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",
) )
} }
}

View File

@ -8,16 +8,18 @@ class NaiveTrustManager : X509TrustManager {
override fun checkClientTrusted( override fun checkClientTrusted(
chain: Array<out X509Certificate>?, chain: Array<out X509Certificate>?,
authType: String?, authType: String?,
) {} ) {
}
override fun checkServerTrusted( override fun checkServerTrusted(
chain: Array<out X509Certificate>?, chain: Array<out X509Certificate>?,
authType: String?, authType: String?,
) {} ) {
}
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()
} }

View File

@ -1,8 +1,7 @@
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.*
actual class DateUtils { actual class DateUtils {
actual companion object { actual companion object {

View File

@ -4,19 +4,13 @@ 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)
}
actual fun SelfossModel.Item.getImages(): ArrayList<String> { actual fun SelfossModel.Item.getImages(): ArrayList<String> {
val allImages = ArrayList<String>() val allImages = ArrayList<String>()
@ -34,16 +28,14 @@ actual fun SelfossModel.Item.getImages(): ArrayList<String> {
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 +43,3 @@ actual fun constructUrl(
baseUriBuilder.toString() baseUriBuilder.toString()
} }
}

View File

@ -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

View File

@ -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)
}
} }
} }

View File

@ -141,7 +141,7 @@ class SelfossModel {
} }
if (stringUrl.isEmptyOrNullOrNullString()) { if (stringUrl.isEmptyOrNullOrNullString()) {
throw Exception("Link ${link} was translated to ${stringUrl}, but was empty. Handle this.") throw Exception("Link $link was translated to $stringUrl, but was empty. Handle this.")
} }
return stringUrl return stringUrl
@ -172,12 +172,11 @@ class SelfossModel {
// TODO: this seems to be super slow. // TODO: this seems to be super slow.
object TagsListSerializer : KSerializer<List<String>> { object TagsListSerializer : KSerializer<List<String>> {
override fun deserialize(decoder: Decoder): List<String> { override fun deserialize(decoder: Decoder): List<String> =
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 +187,7 @@ class SelfossModel {
) { ) {
encoder.encodeCollection( encoder.encodeCollection(
PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING), PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING),
value.size value.size,
) { this.toString() } ) { this.toString() }
} }
} }
@ -204,9 +203,10 @@ class SelfossModel {
} }
override val descriptor: SerialDescriptor override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor( get() =
PrimitiveSerialDescriptor(
"BooleanOrIntForSomeSelfossVersions", "BooleanOrIntForSomeSelfossVersions",
PrimitiveKind.BOOLEAN PrimitiveKind.BOOLEAN,
) )
override fun serialize( override fun serialize(

View File

@ -1,12 +1,21 @@
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
@ -170,8 +179,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 +190,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 +242,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 +259,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 +276,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 +293,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,7 +365,8 @@ class Repository(
): Boolean { ): Boolean {
var response = false var response = false
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
response = api.createSourceForVersion( response = api
.createSourceForVersion(
title, title,
url, url,
spout, spout,
@ -407,13 +412,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 +426,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 +440,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 +452,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 {
@ -578,16 +582,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 +625,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 +639,5 @@ class Repository(
_selectedSource = null _selectedSource = null
} }
fun getSelectedSource(): SelfossModel.SourceDetail? { fun getSelectedSource(): SelfossModel.SourceDetail? = _selectedSource
return _selectedSource
}
} }

View File

@ -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(

View File

@ -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,7 +32,6 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse {
} }
SuccessResponse(false) SuccessResponse(false)
} }
}
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> { suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
try { try {

View File

@ -33,16 +33,18 @@ 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) { 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 +107,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
private fun hasLoginInfo() = private fun hasLoginInfo() =
appSettingsService.getUserName().isNotEmpty() && appSettingsService.getUserName().isNotEmpty() &&
appSettingsService.getPassword() appSettingsService
.getPassword()
.isNotEmpty() .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 +131,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 +156,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 +176,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
}, },
) )
private fun shouldHaveNewLogout() = private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= 5 // 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 +188,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 +211,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 +253,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 +282,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 +309,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 +336,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 +363,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 +390,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 +417,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 +444,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 +467,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 +494,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 +521,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 +548,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 +575,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(
@ -571,8 +608,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
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(
@ -625,8 +664,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
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(
@ -680,8 +721,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
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 +748,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(

View File

@ -2,7 +2,9 @@ package bou.amine.apps.readerforselfossv2.service
import com.russhwolf.settings.Settings import com.russhwolf.settings.Settings
class AppSettingsService(acraSenderServiceProcess: Boolean = false) { class AppSettingsService(
acraSenderServiceProcess: Boolean = false,
) {
val settings: Settings = val settings: Settings =
if (acraSenderServiceProcess) { if (acraSenderServiceProcess) {
ACRASettings() ACRASettings()
@ -11,37 +13,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 = 360
private var _markOnScroll: Boolean? = null private var markOnScroll: Boolean? = null
private var _activeAlignment: Int? = null private var activeAlignment: Int? = null
private var _fontSize: Int? = null private var fontSize: Int? = null
private var _staticBar: Boolean? = null private var 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 +51,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 +64,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 +80,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,53 +96,53 @@ 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!!
} }
private fun refreshItemsNumber() { private fun refreshItemsNumber() {
_itemsNumber = itemsNumber =
try { try {
settings.getString(API_ITEMS_NUMBER, "20").toInt() settings.getString(API_ITEMS_NUMBER, "20").toInt()
} catch (e: Exception) { } catch (e: Exception) {
@ -150,16 +152,16 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
} }
fun getApiTimeout(): Long { fun getApiTimeout(): Long {
if (_apiTimeout == null) { if (apiTimeout == null) {
refreshApiTimeout() refreshApiTimeout()
} }
return _apiTimeout!! return apiTimeout!!
} }
private fun secToMs(n: Long) = n * 1000 private fun secToMs(n: Long) = n * 1000
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, "60")
@ -177,229 +179,229 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
} }
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, "360").toLong()
if (_refreshMinutes <= 15) { if (refreshMinutes <= 15) {
_refreshMinutes = 15 refreshMinutes = 15
} }
} }
fun getRefreshMinutes(): Long { fun getRefreshMinutes(): Long {
if (_refreshMinutes != 360L) { if (refreshMinutes != 360L) {
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 ?: 16
} }
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 +480,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

View File

@ -17,7 +17,11 @@ 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 Exception("Couldn't parse $this")
} else { } else {
throw Exception("Unrecognized format for $this") throw Exception("Unrecognized format for $this")
} }

View File

@ -1,6 +1,9 @@
package bou.amine.apps.readerforselfossv2.utils package bou.amine.apps.readerforselfossv2.utils
enum class ItemType(val position: Int, val type: String) { 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"),

View File

@ -14,21 +14,23 @@ class DatesTest {
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)

View File

@ -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")
}
} }

View File

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

View File

@ -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")
}
} }

View File

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