chore: code style fixes for ktlint
This commit is contained in:
parent
5035392aff
commit
4fbebf2954
36
.editorconfig
Normal file
36
.editorconfig
Normal file
@ -0,0 +1,36 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
|
||||
[.editorconfig]
|
||||
insert_final_newline = false
|
||||
ij_kotlin_line_break_after_multiline_when_entry = false
|
||||
|
||||
[*.{kt,kts}]
|
||||
# Disable wildcard imports entirely
|
||||
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||
end_of_line = lf
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
|
||||
ij_kotlin_indent_before_arrow_on_new_line = false
|
||||
ij_kotlin_line_break_after_multiline_when_entry = true
|
||||
ij_kotlin_packages_to_use_import_on_demand = unset
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
ktlint_argument_list_wrapping_ignore_when_parameter_count_greater_or_equal_than = unset
|
||||
ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than = 4
|
||||
ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 1
|
||||
ktlint_code_style = ktlint_official
|
||||
ktlint_enum_entry_name_casing = upper_or_camel_cases
|
||||
ktlint_function_naming_ignore_when_annotated_with = unset
|
||||
ktlint_function_signature_body_expression_wrapping = multiline
|
||||
ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 2
|
||||
ktlint_ignore_back_ticked_identifier = false
|
||||
ktlint_property_naming_constant_naming = screaming_snake_case
|
||||
max_line_length = 140
|
||||
|
||||
[**/build]
|
||||
ktlint = disabled
|
@ -20,9 +20,9 @@ jobs:
|
||||
- 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
|
||||
- name: Linting...
|
||||
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build' || true
|
||||
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build'
|
||||
- name: Detecting...
|
||||
run: ./detekt-cli-1.23.1/bin/detekt-cli --all-rules --excludes '**/shared/build/**/*.kt' || true
|
||||
build:
|
||||
needs: Lint
|
||||
uses: ./.gitea/workflows/common_build.yml
|
||||
uses: ./.gitea/workflows/common_build.yml
|
||||
|
@ -20,65 +20,72 @@ import org.hamcrest.Matchers.hasToString
|
||||
fun performLogin(someUrl: String? = null) {
|
||||
onView(withId(R.id.urlView)).perform(click()).perform(
|
||||
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())
|
||||
}
|
||||
|
||||
fun loginAndInitHome() {
|
||||
|
||||
performLogin()
|
||||
onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed()))
|
||||
onView(withText("OK")).perform(click())
|
||||
}
|
||||
|
||||
fun changeAndCancelSetting(oldValue: String, newValue: String, openSettingItem: () -> Unit) {
|
||||
fun changeAndCancelSetting(
|
||||
oldValue: String,
|
||||
newValue: String,
|
||||
openSettingItem: () -> Unit,
|
||||
) {
|
||||
openSettingItem()
|
||||
onView(
|
||||
withId(android.R.id.edit)
|
||||
withId(android.R.id.edit),
|
||||
).perform(replaceText(newValue))
|
||||
onView(
|
||||
withId(android.R.id.button2)
|
||||
withId(android.R.id.button2),
|
||||
).perform(click())
|
||||
openSettingItem()
|
||||
onView(
|
||||
withId(android.R.id.edit)
|
||||
withId(android.R.id.edit),
|
||||
).check(matches(withText(oldValue)))
|
||||
onView(
|
||||
withText(newValue)
|
||||
withText(newValue),
|
||||
).check(doesNotExist())
|
||||
onView(
|
||||
withId(android.R.id.button2)
|
||||
withId(android.R.id.button2),
|
||||
).perform(click())
|
||||
}
|
||||
|
||||
fun changeAndSaveSetting(oldValue: String, newValue: String, openSettingItem: () -> Unit) {
|
||||
fun changeAndSaveSetting(
|
||||
oldValue: String,
|
||||
newValue: String,
|
||||
openSettingItem: () -> Unit,
|
||||
) {
|
||||
openSettingItem()
|
||||
onView(
|
||||
withId(android.R.id.edit)
|
||||
withId(android.R.id.edit),
|
||||
).perform(replaceText(newValue))
|
||||
onView(
|
||||
withId(android.R.id.button1)
|
||||
withId(android.R.id.button1),
|
||||
).perform(click())
|
||||
openSettingItem()
|
||||
onView(
|
||||
withId(android.R.id.edit)
|
||||
withId(android.R.id.edit),
|
||||
).check(matches(withText(newValue)))
|
||||
if (oldValue.isNotEmpty()) {
|
||||
onView(
|
||||
withText(oldValue)
|
||||
withText(oldValue),
|
||||
).check(doesNotExist())
|
||||
}
|
||||
onView(
|
||||
withId(android.R.id.button2)
|
||||
withId(android.R.id.button2),
|
||||
).perform(click())
|
||||
}
|
||||
|
||||
fun testPreferencesFromArray(
|
||||
context: Context,
|
||||
@ArrayRes arrayRes: Int,
|
||||
openSettingItem: () -> Unit
|
||||
openSettingItem: () -> Unit,
|
||||
) {
|
||||
openSettingItem()
|
||||
context.resources.getStringArray(arrayRes).forEach { res ->
|
||||
@ -90,20 +97,25 @@ fun testPreferencesFromArray(
|
||||
}
|
||||
}
|
||||
|
||||
fun testAddSourceWithUrl(url: String, sourceName: String) {
|
||||
fun testAddSourceWithUrl(
|
||||
url: String,
|
||||
sourceName: String,
|
||||
) {
|
||||
onView(withId(R.id.fab))
|
||||
.perform(click())
|
||||
onView(withId(R.id.nameInput))
|
||||
.perform(click()).perform(typeTextIntoFocusedView(sourceName))
|
||||
.perform(click())
|
||||
.perform(typeTextIntoFocusedView(sourceName))
|
||||
onView(withId(R.id.sourceUri))
|
||||
.perform(click())
|
||||
.perform(typeTextIntoFocusedView(url))
|
||||
onView(withId(R.id.tags))
|
||||
.perform(click()).perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
|
||||
.perform(click())
|
||||
.perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
|
||||
onView(withId(R.id.spoutsSpinner))
|
||||
.perform(click())
|
||||
onData(hasToString("RSS Feed")).perform(click())
|
||||
onView(withId(R.id.saveBtn))
|
||||
.perform(click())
|
||||
onView(withText(sourceName)).check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
||||
|
@ -25,8 +25,9 @@ import org.hamcrest.Matcher
|
||||
import org.hamcrest.Matchers
|
||||
import org.hamcrest.TypeSafeMatcher
|
||||
|
||||
|
||||
fun withError(@StringRes id: Int): TypeSafeMatcher<View?> {
|
||||
fun withError(
|
||||
@StringRes id: Int,
|
||||
): TypeSafeMatcher<View?> {
|
||||
return object : TypeSafeMatcher<View?>() {
|
||||
override fun matchesSafely(view: View?): Boolean {
|
||||
if (view == null) {
|
||||
@ -48,11 +49,11 @@ fun withError(@StringRes id: Int): TypeSafeMatcher<View?> {
|
||||
}
|
||||
}
|
||||
|
||||
fun isPopupWindow(): Matcher<Root> {
|
||||
return isPlatformPopup()
|
||||
}
|
||||
fun isPopupWindow(): Matcher<Root> = isPlatformPopup()
|
||||
|
||||
fun withDrawable(@DrawableRes id: Int) = object : TypeSafeMatcher<View>() {
|
||||
fun withDrawable(
|
||||
@DrawableRes id: Int,
|
||||
) = object : TypeSafeMatcher<View>() {
|
||||
override fun describeTo(description: Description) {
|
||||
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>? {
|
||||
return allOf(
|
||||
fun hasBottombarItemText(
|
||||
@StringRes id: Int,
|
||||
): Matcher<View>? =
|
||||
allOf(
|
||||
withResourceName("fixed_bottom_navigation_icon"),
|
||||
withParent(
|
||||
allOf(
|
||||
withResourceName("fixed_bottom_navigation_icon_container"),
|
||||
hasSibling(withText(id))
|
||||
)
|
||||
)
|
||||
hasSibling(withText(id)),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun withSettingsCheckboxWidget(@StringRes id: Int): Matcher<View>? {
|
||||
return allOf(
|
||||
fun withSettingsCheckboxWidget(
|
||||
@StringRes id: Int,
|
||||
): Matcher<View>? =
|
||||
allOf(
|
||||
withId(android.R.id.switch_widget),
|
||||
withParent(
|
||||
withSettingsCheckboxFrame(id)
|
||||
)
|
||||
withSettingsCheckboxFrame(id),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun withSettingsCheckboxFrame(@StringRes id: Int): Matcher<View>? {
|
||||
return allOf(
|
||||
fun withSettingsCheckboxFrame(
|
||||
@StringRes id: Int,
|
||||
): Matcher<View>? =
|
||||
allOf(
|
||||
withId(android.R.id.widget_frame),
|
||||
hasSibling(
|
||||
allOf(
|
||||
withClassName(Matchers.equalTo(RelativeLayout::class.java.name)),
|
||||
withChild(
|
||||
withText(id)
|
||||
)
|
||||
)
|
||||
)
|
||||
withText(id),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun openMenu() {
|
||||
openActionBarOverflowOrOptionsMenu(
|
||||
ApplicationProvider.getApplicationContext<Context>()
|
||||
ApplicationProvider.getApplicationContext<Context>(),
|
||||
)
|
||||
}
|
||||
}
|
@ -23,7 +23,6 @@ import org.junit.runner.RunWith
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class HomeActivityTest {
|
||||
|
||||
@get:Rule
|
||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||
|
||||
@ -36,13 +35,13 @@ class HomeActivityTest {
|
||||
fun testMenu() {
|
||||
onView(withId(R.id.action_search)).check(matches(isDisplayed())).check(
|
||||
matches(
|
||||
isClickable()
|
||||
)
|
||||
isClickable(),
|
||||
),
|
||||
)
|
||||
onView(withId(R.id.action_filter)).check(matches(isDisplayed())).check(
|
||||
matches(
|
||||
isClickable()
|
||||
)
|
||||
isClickable(),
|
||||
),
|
||||
)
|
||||
openMenu()
|
||||
onView(withText(R.string.readAll)).check(matches(isDisplayed()))
|
||||
@ -57,19 +56,19 @@ class HomeActivityTest {
|
||||
fun testMenuActions() {
|
||||
onView(withId(R.id.action_search)).perform(click())
|
||||
onView(
|
||||
withId(R.id.search_src_text)
|
||||
withId(R.id.search_src_text),
|
||||
).check(matches(isFocused()))
|
||||
onView(isRoot()).perform(ViewActions.pressBack())
|
||||
|
||||
onView(withId(R.id.action_filter)).perform(click())
|
||||
onView(
|
||||
withText(R.string.filter_item_sources)
|
||||
withText(R.string.filter_item_sources),
|
||||
).check(matches(isDisplayed()))
|
||||
onView(
|
||||
withText(R.string.filter_item_tags)
|
||||
withText(R.string.filter_item_tags),
|
||||
).check(matches(isDisplayed()))
|
||||
onView(
|
||||
withId(R.id.floatingActionButton2)
|
||||
withId(R.id.floatingActionButton2),
|
||||
).check(matches(isDisplayed()))
|
||||
onView(isRoot()).perform(ViewActions.pressBack())
|
||||
|
||||
@ -107,14 +106,13 @@ class HomeActivityTest {
|
||||
fun testEmptyView() {
|
||||
onView(withId(R.id.emptyText)).check(matches(isDisplayed()))
|
||||
onView(
|
||||
hasBottombarItemText(R.string.tab_new)
|
||||
hasBottombarItemText(R.string.tab_new),
|
||||
).check(matches(isDisplayed())).check(matches(isSelected()))
|
||||
onView(
|
||||
hasBottombarItemText(R.string.tab_read)
|
||||
hasBottombarItemText(R.string.tab_read),
|
||||
).check(matches(isDisplayed())).check(matches(not(isSelected())))
|
||||
onView(
|
||||
hasBottombarItemText(R.string.tab_favs)
|
||||
hasBottombarItemText(R.string.tab_favs),
|
||||
).check(matches(isDisplayed())).check(matches(not(isSelected())))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import org.junit.runner.RunWith
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class LoginActivityTest {
|
||||
|
||||
@get:Rule
|
||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||
|
||||
@ -37,26 +36,32 @@ class LoginActivityTest {
|
||||
|
||||
@Before
|
||||
fun registerIdlingResource() {
|
||||
IdlingRegistry.getInstance()
|
||||
IdlingRegistry
|
||||
.getInstance()
|
||||
.register(CountingIdlingResourceSingleton.countingIdlingResource)
|
||||
}
|
||||
|
||||
@After
|
||||
fun unregisterIdlingResource() {
|
||||
IdlingRegistry.getInstance()
|
||||
IdlingRegistry
|
||||
.getInstance()
|
||||
.unregister(CountingIdlingResourceSingleton.countingIdlingResource)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun viewIsInitialized() {
|
||||
onView(withId(R.id.urlView)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.selfSigned)).check(matches(isDisplayed())).check(matches(isNotChecked()))
|
||||
onView(withId(R.id.selfSigned))
|
||||
.check(matches(isDisplayed()))
|
||||
.check(matches(isNotChecked()))
|
||||
.check(
|
||||
matches(isClickable())
|
||||
matches(isClickable()),
|
||||
)
|
||||
onView(withId(R.id.withLogin)).check(matches(isDisplayed()))
|
||||
.check(matches(isNotChecked())).check(
|
||||
matches(isClickable())
|
||||
onView(withId(R.id.withLogin))
|
||||
.check(matches(isDisplayed()))
|
||||
.check(matches(isNotChecked()))
|
||||
.check(
|
||||
matches(isClickable()),
|
||||
)
|
||||
}
|
||||
|
||||
@ -80,4 +85,4 @@ class LoginActivityTest {
|
||||
performLogin()
|
||||
onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,11 +26,9 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class SettingsActivityGeneralTest {
|
||||
|
||||
@get:Rule
|
||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||
|
||||
@ -38,7 +36,7 @@ class SettingsActivityGeneralTest {
|
||||
fun init() {
|
||||
loginAndInitHome()
|
||||
openActionBarOverflowOrOptionsMenu(
|
||||
ApplicationProvider.getApplicationContext()
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
)
|
||||
onView(withText(R.string.title_activity_settings)).perform(click())
|
||||
onView(withText(R.string.pref_header_general)).perform(click())
|
||||
@ -48,68 +46,75 @@ class SettingsActivityGeneralTest {
|
||||
fun testGeneral() {
|
||||
onView(withText(R.string.pref_api_items_number_title)).check(matches(isDisplayed()))
|
||||
onView(
|
||||
withSettingsCheckboxWidget(R.string.pref_general_infinite_loading_title)
|
||||
withSettingsCheckboxWidget(R.string.pref_general_infinite_loading_title),
|
||||
).check(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(), not(isChecked())
|
||||
)
|
||||
)
|
||||
isDisplayed(),
|
||||
not(isChecked()),
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withText(R.string.pref_general_category_links)).check(matches(isDisplayed()))
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_article_viewer_title)).check(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(), isChecked()
|
||||
)
|
||||
)
|
||||
isDisplayed(),
|
||||
isChecked(),
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxWidget(R.string.reader_static_bar_title)).check(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(), not(isChecked())
|
||||
)
|
||||
)
|
||||
isDisplayed(),
|
||||
not(isChecked()),
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||
matches(
|
||||
isEnabled()
|
||||
)
|
||||
isEnabled(),
|
||||
),
|
||||
)
|
||||
onView(withText(R.string.pref_general_category_displaying)).check(matches(isDisplayed()))
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).check(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(), not(isChecked())
|
||||
)
|
||||
)
|
||||
isDisplayed(),
|
||||
not(isChecked()),
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxWidget(R.string.card_height_title)).check(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(), not(isChecked())
|
||||
)
|
||||
)
|
||||
isDisplayed(),
|
||||
not(isChecked()),
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(
|
||||
matches(
|
||||
not(isEnabled())
|
||||
)
|
||||
not(isEnabled()),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxWidget(R.string.switch_unread_count_title)).check(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(), isChecked()
|
||||
)
|
||||
)
|
||||
isDisplayed(),
|
||||
isChecked(),
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withId(R.id.settings)).perform(swipeUp())
|
||||
onView(withSettingsCheckboxWidget(R.string.display_all_counts_title)).check(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(), not(isChecked())
|
||||
)
|
||||
)
|
||||
isDisplayed(),
|
||||
not(isChecked()),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -120,25 +125,25 @@ class SettingsActivityGeneralTest {
|
||||
|
||||
// Value check
|
||||
onView(
|
||||
withId(android.R.id.edit)
|
||||
withId(android.R.id.edit),
|
||||
).perform(replaceText("AVC"))
|
||||
.check(matches(withText("")))
|
||||
// TODO: should check message error. Not working for api level 30+
|
||||
onView(
|
||||
withId(android.R.id.edit)
|
||||
withId(android.R.id.edit),
|
||||
).perform(replaceText("-1"))
|
||||
.check(matches(withText("")))
|
||||
// TODO: should check message error. Not working for api level 30+
|
||||
onView(
|
||||
withId(android.R.id.edit)
|
||||
withId(android.R.id.edit),
|
||||
).perform(replaceText("300"))
|
||||
.check(matches(withText("")))
|
||||
onView(
|
||||
withId(android.R.id.edit)
|
||||
withId(android.R.id.edit),
|
||||
).perform(typeTextIntoFocusedView("300"))
|
||||
.check(matches(withText("30")))
|
||||
onView(
|
||||
withId(android.R.id.edit)
|
||||
withId(android.R.id.edit),
|
||||
).perform(replaceText("10"))
|
||||
.check(matches(withText("10")))
|
||||
onView(isRoot()).perform(ViewActions.pressBack())
|
||||
@ -157,18 +162,18 @@ class SettingsActivityGeneralTest {
|
||||
// article viewer settings
|
||||
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||
matches(
|
||||
isEnabled()
|
||||
)
|
||||
isEnabled(),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_article_viewer_title)).perform(click())
|
||||
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||
matches(
|
||||
not(isEnabled())
|
||||
)
|
||||
not(isEnabled()),
|
||||
),
|
||||
)
|
||||
|
||||
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(not(isEnabled())))
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).perform(click())
|
||||
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(isEnabled()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,11 +21,9 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class SettingsActivityOfflineTest {
|
||||
|
||||
@get:Rule
|
||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||
|
||||
@ -38,7 +36,7 @@ class SettingsActivityOfflineTest {
|
||||
}
|
||||
loginAndInitHome()
|
||||
openActionBarOverflowOrOptionsMenu(
|
||||
ApplicationProvider.getApplicationContext()
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
)
|
||||
onView(withText(R.string.title_activity_settings)).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(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(), not(isChecked())
|
||||
)
|
||||
)
|
||||
isDisplayed(),
|
||||
not(isChecked()),
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_items_caching)).check(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(), not(isChecked())
|
||||
)
|
||||
)
|
||||
isDisplayed(),
|
||||
not(isChecked()),
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_items_caching)).check(
|
||||
matches(
|
||||
isEnabled()
|
||||
)
|
||||
isEnabled(),
|
||||
),
|
||||
)
|
||||
onView(withText(R.string.pref_periodic_refresh_minutes_title)).check(
|
||||
matches(
|
||||
allOf(isNotEnabled(), isDisplayed())
|
||||
)
|
||||
allOf(isNotEnabled(), isDisplayed()),
|
||||
),
|
||||
)
|
||||
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_refresh_when_charging)).check(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(), not(isChecked())
|
||||
)
|
||||
)
|
||||
isDisplayed(),
|
||||
not(isChecked()),
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
|
||||
matches(
|
||||
isNotEnabled()
|
||||
)
|
||||
isNotEnabled(),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_notify_new_items)).check(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(), not(isChecked())
|
||||
)
|
||||
)
|
||||
isDisplayed(),
|
||||
not(isChecked()),
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
|
||||
matches(
|
||||
isNotEnabled()
|
||||
)
|
||||
isNotEnabled(),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_update_sources)).check(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(), isChecked()
|
||||
)
|
||||
)
|
||||
isDisplayed(),
|
||||
isChecked(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -111,50 +114,50 @@ class SettingsActivityOfflineTest {
|
||||
onView(withText(R.string.pref_switch_items_caching_on)).check(matches(isDisplayed()))
|
||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_items_caching)).check(
|
||||
matches(
|
||||
isEnabled()
|
||||
)
|
||||
isEnabled(),
|
||||
),
|
||||
)
|
||||
onView(withText(R.string.pref_periodic_refresh_minutes_title)).check(
|
||||
matches(
|
||||
isNotEnabled()
|
||||
)
|
||||
isNotEnabled(),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
|
||||
matches(
|
||||
isNotEnabled()
|
||||
)
|
||||
isNotEnabled(),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
|
||||
matches(
|
||||
isNotEnabled()
|
||||
)
|
||||
isNotEnabled(),
|
||||
),
|
||||
)
|
||||
|
||||
onView(withText(R.string.pref_switch_periodic_refresh_off)).check(
|
||||
matches(
|
||||
isDisplayed()
|
||||
)
|
||||
isDisplayed(),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).perform(click())
|
||||
onView(withText(R.string.pref_switch_periodic_refresh_on)).check(
|
||||
matches(
|
||||
isDisplayed()
|
||||
)
|
||||
isDisplayed(),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxFrame(R.string.pref_periodic_refresh_minutes_title)).check(
|
||||
matches(
|
||||
isEnabled()
|
||||
)
|
||||
isEnabled(),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
|
||||
matches(
|
||||
isEnabled()
|
||||
)
|
||||
isEnabled(),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
|
||||
matches(
|
||||
isEnabled()
|
||||
)
|
||||
isEnabled(),
|
||||
),
|
||||
)
|
||||
changeAndCancelSetting("360", "123") {
|
||||
onView(withText(R.string.pref_periodic_refresh_minutes_title)).perform(click())
|
||||
@ -166,4 +169,4 @@ class SettingsActivityOfflineTest {
|
||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).perform(click())
|
||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_update_sources)).perform(click())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,11 +19,9 @@ import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class SettingsActivityReaderTest {
|
||||
|
||||
@get:Rule
|
||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||
|
||||
@ -36,7 +34,7 @@ class SettingsActivityReaderTest {
|
||||
}
|
||||
loginAndInitHome()
|
||||
openActionBarOverflowOrOptionsMenu(
|
||||
ApplicationProvider.getApplicationContext()
|
||||
ApplicationProvider.getApplicationContext(),
|
||||
)
|
||||
onView(withText(R.string.title_activity_settings)).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(
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(), not(
|
||||
isChecked()
|
||||
)
|
||||
)
|
||||
)
|
||||
isDisplayed(),
|
||||
not(
|
||||
isChecked(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withText(R.string.pref_content_reader_font_size)).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.settings_reader_font)).check(matches(isDisplayed()))
|
||||
@ -61,14 +60,14 @@ class SettingsActivityReaderTest {
|
||||
fun testReaderActions() {
|
||||
onView(withText(R.string.pref_switch_actions_pager_scroll_off)).check(
|
||||
matches(
|
||||
isDisplayed()
|
||||
)
|
||||
isDisplayed(),
|
||||
),
|
||||
)
|
||||
onView(withSettingsCheckboxFrame(R.string.pref_switch_actions_pager_scroll)).perform(click())
|
||||
onView(withText(R.string.pref_switch_actions_pager_scroll_on)).check(
|
||||
matches(
|
||||
isDisplayed()
|
||||
)
|
||||
isDisplayed(),
|
||||
),
|
||||
)
|
||||
|
||||
onView(withText(R.string.pref_content_reader_font_size)).perform(click())
|
||||
@ -83,4 +82,4 @@ class SettingsActivityReaderTest {
|
||||
onView(withText(R.string.settings_reader_font)).perform(click())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import org.junit.runner.RunWith
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@LargeTest
|
||||
class SettingsActivityTest {
|
||||
|
||||
@get:Rule
|
||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||
lateinit var context: Context
|
||||
@ -35,10 +34,8 @@ class SettingsActivityTest {
|
||||
onView(withText(R.string.title_activity_settings)).perform(click())
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testAllSettings() {
|
||||
|
||||
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_offline)).check(matches(isDisplayed()))
|
||||
@ -48,14 +45,13 @@ class SettingsActivityTest {
|
||||
matches(
|
||||
allOf(
|
||||
isDisplayed(),
|
||||
not(isSelected())
|
||||
)
|
||||
)
|
||||
not(isSelected()),
|
||||
),
|
||||
),
|
||||
)
|
||||
onView(withText(R.string.action_about)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testThemes() {
|
||||
testPreferencesFromArray(context, R.array.ModeTitles) {
|
||||
@ -63,7 +59,6 @@ class SettingsActivityTest {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testExperimentail() {
|
||||
onView(withText(R.string.pref_header_experimental)).perform(click())
|
||||
@ -75,13 +70,11 @@ class SettingsActivityTest {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testBugReports() {
|
||||
onView(withText(R.string.pref_switch_disable_acra)).perform(click())
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testLinks() {
|
||||
onView(withText(R.string.pref_header_links)).perform(click())
|
||||
@ -91,10 +84,9 @@ class SettingsActivityTest {
|
||||
onView(withText(R.string.translation)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun testAbout() {
|
||||
onView(withText(R.string.action_about)).perform(click())
|
||||
onView(withText("ACRA")).check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,11 +41,10 @@ class SourcesActivityTest {
|
||||
fun addSource() {
|
||||
testAddSourceWithUrl(
|
||||
"https://lorem-rss.herokuapp.com/feed?unit=year&interval=1&length=10",
|
||||
sourceName
|
||||
sourceName,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun addSourceCheckContent() {
|
||||
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.refresh_dialog_message)).check(matches(isDisplayed()))
|
||||
onView(
|
||||
withId(android.R.id.button1)
|
||||
withId(android.R.id.button1),
|
||||
).perform(click())
|
||||
Thread.sleep(10000)
|
||||
onView(withId(R.id.swipeRefreshLayout)).perform(swipeDown())
|
||||
@ -74,10 +73,9 @@ class SourcesActivityTest {
|
||||
onView(withText(sourceName)).check(doesNotExist())
|
||||
}
|
||||
|
||||
|
||||
private fun goToSources() {
|
||||
openMenu()
|
||||
onView(withText(R.string.menu_home_sources))
|
||||
.perform(click())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,10 @@ import org.kodein.di.instance
|
||||
import java.security.MessageDigest
|
||||
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 elementsShown: ItemType = ItemType.UNREAD
|
||||
@ -171,11 +174,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
getElementsAccordingToTab()
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this@HomeActivity,
|
||||
"Found null when swiping at positon $position.",
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
this@HomeActivity,
|
||||
"Found null when swiping at positon $position.",
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -200,15 +204,18 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
tabNewBadge =
|
||||
TextBadgeItem()
|
||||
.setText("")
|
||||
.setHideOnSelect(false).hide(false)
|
||||
.setHideOnSelect(false)
|
||||
.hide(false)
|
||||
tabArchiveBadge =
|
||||
TextBadgeItem()
|
||||
.setText("")
|
||||
.setHideOnSelect(false).hide(false)
|
||||
.setHideOnSelect(false)
|
||||
.hide(false)
|
||||
tabStarredBadge =
|
||||
TextBadgeItem()
|
||||
.setText("")
|
||||
.setHideOnSelect(false).hide(false)
|
||||
.setHideOnSelect(false)
|
||||
.hide(false)
|
||||
|
||||
if (appSettingsService.isDisplayUnreadCountEnabled()) {
|
||||
lifecycleScope.launch {
|
||||
@ -236,14 +243,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
BottomNavigationItem(
|
||||
R.drawable.ic_tab_fiber_new_black_24dp,
|
||||
getString(R.string.tab_new),
|
||||
)
|
||||
.setBadgeItem(tabNewBadge)
|
||||
).setBadgeItem(tabNewBadge)
|
||||
val tabArchive =
|
||||
BottomNavigationItem(
|
||||
R.drawable.ic_tab_archive_black_24dp,
|
||||
getString(R.string.tab_read),
|
||||
)
|
||||
.setBadgeItem(tabArchiveBadge)
|
||||
).setBadgeItem(tabArchiveBadge)
|
||||
val tabStarred =
|
||||
BottomNavigationItem(
|
||||
R.drawable.ic_tab_favorite_black_24dp,
|
||||
@ -425,17 +430,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
binding.recyclerView.addOnScrollListener(recyclerViewScrollListener)
|
||||
}
|
||||
|
||||
private fun getLastVisibleItem(): Int {
|
||||
return when (val manager = binding.recyclerView.layoutManager) {
|
||||
private fun getLastVisibleItem(): Int =
|
||||
when (val manager = binding.recyclerView.layoutManager) {
|
||||
is StaggeredGridLayoutManager ->
|
||||
manager.findLastCompletelyVisibleItemPositions(
|
||||
null,
|
||||
).last()
|
||||
manager
|
||||
.findLastCompletelyVisibleItemPositions(
|
||||
null,
|
||||
).last()
|
||||
|
||||
is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition()
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun mayBeEmpty() =
|
||||
if (items.isEmpty()) {
|
||||
@ -577,7 +582,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
messageRes: Int,
|
||||
doFn: () -> Unit,
|
||||
) {
|
||||
AlertDialog.Builder(this@HomeActivity)
|
||||
AlertDialog
|
||||
.Builder(this@HomeActivity)
|
||||
.setMessage(messageRes)
|
||||
.setTitle(titleRes)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> doFn() }
|
||||
@ -589,7 +595,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.issue_tracker -> {
|
||||
baseContext.openUrlInBrowser(AppSettingsService.trackerUrl)
|
||||
baseContext.openUrlInBrowser(AppSettingsService.BUG_URL)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -606,18 +612,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val updatedRemote = repository.updateRemote()
|
||||
if (updatedRemote) {
|
||||
Toast.makeText(
|
||||
this@HomeActivity,
|
||||
R.string.refresh_success_response,
|
||||
Toast.LENGTH_LONG,
|
||||
)
|
||||
.show()
|
||||
Toast
|
||||
.makeText(
|
||||
this@HomeActivity,
|
||||
R.string.refresh_success_response,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this@HomeActivity,
|
||||
R.string.refresh_failer_message,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
this@HomeActivity,
|
||||
R.string.refresh_failer_message,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
CountingIdlingResourceSingleton.decrement()
|
||||
}
|
||||
@ -633,25 +640,26 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val success = repository.markAllAsRead(items)
|
||||
if (success) {
|
||||
Toast.makeText(
|
||||
this@HomeActivity,
|
||||
R.string.all_posts_read,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
this@HomeActivity,
|
||||
R.string.all_posts_read,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
tabNewBadge.removeBadge()
|
||||
|
||||
getElementsAccordingToTab()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this@HomeActivity,
|
||||
R.string.all_posts_not_read,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
this@HomeActivity,
|
||||
R.string.all_posts_not_read,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
handleListResult()
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
CountingIdlingResourceSingleton.decrement()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -661,7 +669,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
R.id.action_disconnect -> {
|
||||
needsConfirmation(
|
||||
R.string.confirm_disconnect_title,
|
||||
R.string.confirm_disconnect_description
|
||||
R.string.confirm_disconnect_description,
|
||||
) {
|
||||
runBlocking {
|
||||
repository.logout()
|
||||
@ -702,7 +710,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
private fun handleRecurringTask() {
|
||||
if (appSettingsService.isPeriodicRefreshEnabled()) {
|
||||
val myConstraints =
|
||||
Constraints.Builder()
|
||||
Constraints
|
||||
.Builder()
|
||||
.setRequiresBatteryNotLow(true)
|
||||
.setRequiresCharging(appSettingsService.isRefreshWhenChargingOnlyEnabled())
|
||||
.setRequiresStorageNotLow(true)
|
||||
@ -711,19 +720,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
val backgroundWork =
|
||||
PeriodicWorkRequestBuilder<LoadingWorker>(
|
||||
appSettingsService.getRefreshMinutes(),
|
||||
TimeUnit.MINUTES
|
||||
)
|
||||
.setConstraints(myConstraints)
|
||||
TimeUnit.MINUTES,
|
||||
).setConstraints(myConstraints)
|
||||
.addTag("selfoss-loading")
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(
|
||||
baseContext,
|
||||
).enqueueUniquePeriodicWork(
|
||||
"selfoss-loading",
|
||||
ExistingPeriodicWorkPolicy.KEEP,
|
||||
backgroundWork
|
||||
)
|
||||
WorkManager
|
||||
.getInstance(
|
||||
baseContext,
|
||||
).enqueueUniquePeriodicWork(
|
||||
"selfoss-loading",
|
||||
ExistingPeriodicWorkPolicy.KEEP,
|
||||
backgroundWork,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +84,9 @@ class ImageActivity : AppCompatActivity() {
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
|
||||
private inner class ScreenSlidePagerAdapter(
|
||||
fa: FragmentActivity,
|
||||
) : FragmentStateAdapter(fa) {
|
||||
override fun getItemCount(): Int = allImages.size
|
||||
|
||||
override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position])
|
||||
|
@ -30,7 +30,9 @@ import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
class LoginActivity : AppCompatActivity(), DIAware {
|
||||
class LoginActivity :
|
||||
AppCompatActivity(),
|
||||
DIAware {
|
||||
private var inValidCount: Int = 0
|
||||
private var isWithLogin = false
|
||||
|
||||
@ -108,7 +110,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
||||
repository.updateApiInformation()
|
||||
ACRA.errorReporter.putCustomData(
|
||||
"SELFOSS_API_VERSION",
|
||||
appSettingsService.getApiVersion().toString()
|
||||
appSettingsService.getApiVersion().toString(),
|
||||
)
|
||||
CountingIdlingResourceSingleton.decrement()
|
||||
}
|
||||
@ -132,9 +134,18 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
||||
binding.passwordView.error = null
|
||||
|
||||
// Store values at the time of the login attempt.
|
||||
val url = binding.urlView.text.toString().trim()
|
||||
val login = binding.loginView.text.toString().trim()
|
||||
val password = binding.passwordView.text.toString().trim()
|
||||
val url =
|
||||
binding.urlView.text
|
||||
.toString()
|
||||
.trim()
|
||||
val login =
|
||||
binding.loginView.text
|
||||
.toString()
|
||||
.trim()
|
||||
val password =
|
||||
binding.passwordView.text
|
||||
.toString()
|
||||
.trim()
|
||||
|
||||
failInvalidUrl(url)
|
||||
failLoginDetails(password, login)
|
||||
@ -151,11 +162,12 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
||||
repository.updateApiInformation()
|
||||
} catch (e: Exception) {
|
||||
if (e.message?.startsWith("No transformation found") == true) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
R.string.application_selfoss_only,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
applicationContext,
|
||||
R.string.application_selfoss_only,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
preferenceError()
|
||||
showProgress(false)
|
||||
}
|
||||
@ -270,7 +282,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
||||
return when (item.itemId) {
|
||||
R.id.issue_tracker -> {
|
||||
val browserIntent =
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.trackerUrl))
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.BUG_URL))
|
||||
startActivity(browserIntent)
|
||||
return true
|
||||
}
|
||||
@ -280,9 +292,9 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
||||
.withAboutIconShown(true)
|
||||
.withAboutVersionShown(true)
|
||||
.withAboutSpecial2("Bug reports")
|
||||
.withAboutSpecial2Description(AppSettingsService.trackerUrl)
|
||||
.withAboutSpecial2Description(AppSettingsService.BUG_URL)
|
||||
.withAboutSpecial1("Project Page")
|
||||
.withAboutSpecial1Description(AppSettingsService.sourceUrl)
|
||||
.withAboutSpecial1Description(AppSettingsService.SOURCE_URL)
|
||||
.start(this)
|
||||
true
|
||||
}
|
||||
@ -290,4 +302,4 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,11 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
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.viewmodel.AppViewModel
|
||||
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
|
||||
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.service.AppSettingsService
|
||||
import com.github.ln_12.library.ConnectivityStatus
|
||||
@ -36,21 +36,23 @@ import org.kodein.di.bind
|
||||
import org.kodein.di.instance
|
||||
import org.kodein.di.singleton
|
||||
|
||||
class MyApp : MultiDexApplication(), DIAware {
|
||||
class MyApp :
|
||||
MultiDexApplication(),
|
||||
DIAware {
|
||||
override val di by DI.lazy {
|
||||
bind<AppSettingsService>() with singleton { AppSettingsService(ACRA.isACRASenderServiceProcess() || TestingHelper().isUnitTest()) }
|
||||
import(networkModule)
|
||||
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
|
||||
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
|
||||
bind<Repository>() with
|
||||
singleton {
|
||||
Repository(
|
||||
instance(),
|
||||
instance(),
|
||||
isConnectionAvailable,
|
||||
instance(),
|
||||
)
|
||||
}
|
||||
singleton {
|
||||
Repository(
|
||||
instance(),
|
||||
instance(),
|
||||
isConnectionAvailable,
|
||||
instance(),
|
||||
)
|
||||
}
|
||||
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
|
||||
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
|
||||
}
|
||||
@ -89,11 +91,12 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
R.string.network_connectivity_lost
|
||||
}
|
||||
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
toastMessage,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
applicationContext,
|
||||
toastMessage,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -151,13 +154,13 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
|
||||
val name = getString(R.string.notification_channel_sync)
|
||||
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 newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
|
||||
val newItemsChannelmChannel =
|
||||
NotificationChannel(
|
||||
AppSettingsService.newItemsChannelId,
|
||||
AppSettingsService.NEW_ITEMS_CHANNEL,
|
||||
newItemsChannelname,
|
||||
newItemsChannelimportance,
|
||||
)
|
||||
@ -199,4 +202,4 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
super.onPause(owner)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,9 @@ import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
class ReaderActivity :
|
||||
AppCompatActivity(),
|
||||
DIAware {
|
||||
private var currentItem: Int = 0
|
||||
|
||||
private lateinit var toolbarMenu: Menu
|
||||
@ -99,19 +101,19 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
oldInstanceState.clear()
|
||||
}
|
||||
|
||||
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) :
|
||||
FragmentStateAdapter(fa) {
|
||||
private inner class ScreenSlidePagerAdapter(
|
||||
fa: FragmentActivity,
|
||||
) : FragmentStateAdapter(fa) {
|
||||
override fun getItemCount(): Int = allItems.size
|
||||
|
||||
override fun createFragment(position: Int): Fragment =
|
||||
ArticleFragment.newInstance(allItems[position])
|
||||
override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position])
|
||||
}
|
||||
|
||||
override fun onKeyDown(
|
||||
keyCode: Int,
|
||||
event: KeyEvent?,
|
||||
): Boolean {
|
||||
return when (keyCode) {
|
||||
): Boolean =
|
||||
when (keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||
val currentFragment =
|
||||
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
||||
@ -130,7 +132,6 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
super.onKeyDown(keyCode, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun alignmentMenu() {
|
||||
val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT
|
||||
@ -229,4 +230,4 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
startActivity(intent)
|
||||
overridePendingTransition(0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,9 @@ import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
class SourcesActivity : AppCompatActivity(), DIAware {
|
||||
class SourcesActivity :
|
||||
AppCompatActivity(),
|
||||
DIAware {
|
||||
private lateinit var binding: ActivitySourcesBinding
|
||||
|
||||
override val di by closestDI()
|
||||
@ -68,11 +70,12 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
||||
binding.recyclerView.adapter = mAdapter
|
||||
mAdapter.notifyDataSetChanged()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this@SourcesActivity,
|
||||
R.string.cant_get_sources,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
this@SourcesActivity,
|
||||
R.string.cant_get_sources,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
CountingIdlingResourceSingleton.decrement()
|
||||
}
|
||||
@ -81,4 +84,4 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
||||
startActivity(Intent(this@SourcesActivity, UpsertSourceActivity::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,9 @@ import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
||||
class UpsertSourceActivity :
|
||||
AppCompatActivity(),
|
||||
DIAware {
|
||||
private var existingSource: SelfossModel.SourceDetail? = null
|
||||
private var mSpoutsValue: String? = null
|
||||
|
||||
@ -105,11 +107,12 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
||||
}
|
||||
|
||||
fun handleSpoutFailure(networkIssue: Boolean = false) {
|
||||
Toast.makeText(
|
||||
this@UpsertSourceActivity,
|
||||
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
this@UpsertSourceActivity,
|
||||
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
binding.progress.visibility = View.GONE
|
||||
}
|
||||
|
||||
@ -192,11 +195,12 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
||||
if (successfullyAddedSource) {
|
||||
finish()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this@UpsertSourceActivity,
|
||||
R.string.cant_create_source,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
this@UpsertSourceActivity,
|
||||
R.string.cant_create_source,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,10 @@ class ItemCardAdapter(
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
||||
private fun handleClickListeners(holderBinding: CardItemBinding, position: Int) {
|
||||
private fun handleClickListeners(
|
||||
holderBinding: CardItemBinding,
|
||||
position: Int,
|
||||
) {
|
||||
holderBinding.favButton.setOnClickListener {
|
||||
val item = items[position]
|
||||
if (item.starred) {
|
||||
@ -96,12 +99,13 @@ class ItemCardAdapter(
|
||||
|
||||
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
|
||||
|
||||
binding.sourceTitleAndDate.text = try {
|
||||
itm.sourceAuthorAndDate()
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("ItemCardAdapter parse date")
|
||||
itm.sourceAuthorOnly()
|
||||
}
|
||||
binding.sourceTitleAndDate.text =
|
||||
try {
|
||||
itm.sourceAuthorAndDate()
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("ItemCardAdapter parse date")
|
||||
itm.sourceAuthorOnly()
|
||||
}
|
||||
|
||||
if (!appSettingsService.isFullHeightCardsEnabled()) {
|
||||
binding.itemImage.maxHeight = imageMaxHeight
|
||||
@ -125,5 +129,7 @@ class ItemCardAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
}
|
||||
inner class ViewHolder(
|
||||
val binding: CardItemBinding,
|
||||
) : RecyclerView.ViewHolder(binding.root)
|
||||
}
|
||||
|
@ -53,12 +53,13 @@ class ItemListAdapter(
|
||||
|
||||
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
|
||||
|
||||
binding.sourceTitleAndDate.text = try {
|
||||
itm.sourceAuthorAndDate()
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("ItemListAdapter parse date")
|
||||
itm.sourceAuthorOnly()
|
||||
}
|
||||
binding.sourceTitleAndDate.text =
|
||||
try {
|
||||
itm.sourceAuthorAndDate()
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("ItemListAdapter parse date")
|
||||
itm.sourceAuthorOnly()
|
||||
}
|
||||
|
||||
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
@ -72,5 +73,7 @@ class ItemListAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
}
|
||||
inner class ViewHolder(
|
||||
val binding: ListItemBinding,
|
||||
) : RecyclerView.ViewHolder(binding.root)
|
||||
}
|
||||
|
@ -18,7 +18,9 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import 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 repository: Repository
|
||||
abstract val binding: ViewBinding
|
||||
@ -45,8 +47,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
||||
app.findViewById(R.id.coordLayout),
|
||||
R.string.marked_as_read,
|
||||
Snackbar.LENGTH_LONG,
|
||||
)
|
||||
.setAction(R.string.undo_string) {
|
||||
).setAction(R.string.undo_string) {
|
||||
unreadItemAtIndex(item, position, false)
|
||||
}
|
||||
|
||||
@ -66,8 +67,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
||||
app.findViewById(R.id.coordLayout),
|
||||
R.string.marked_as_unread,
|
||||
Snackbar.LENGTH_LONG,
|
||||
)
|
||||
.setAction(R.string.undo_string) {
|
||||
).setAction(R.string.undo_string) {
|
||||
readItemAtIndex(item, position, false)
|
||||
}
|
||||
|
||||
@ -77,7 +77,10 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
||||
s.show()
|
||||
}
|
||||
|
||||
protected fun handleLinkOpening(holderBinding: ViewBinding, position: Int) {
|
||||
protected fun handleLinkOpening(
|
||||
holderBinding: ViewBinding,
|
||||
position: Int,
|
||||
) {
|
||||
holderBinding.root.setOnClickListener {
|
||||
repository.setReaderItems(items)
|
||||
c.openItemUrl(
|
||||
|
@ -29,7 +29,8 @@ import org.kodein.di.instance
|
||||
class SourcesListAdapter(
|
||||
private val app: Activity,
|
||||
private val items: ArrayList<SelfossModel.SourceDetail>,
|
||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
|
||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(),
|
||||
DIAware {
|
||||
private val c: Context = app.baseContext
|
||||
private lateinit var binding: SourceListItemBinding
|
||||
|
||||
@ -61,11 +62,12 @@ class SourcesListAdapter(
|
||||
notifyItemRemoved(position)
|
||||
notifyItemRangeChanged(position, itemCount)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
app,
|
||||
R.string.can_delete_source,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
app,
|
||||
R.string.can_delete_source,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -99,5 +101,7 @@ class SourcesListAdapter(
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView)
|
||||
inner class ViewHolder(
|
||||
val mView: ConstraintLayout,
|
||||
) : RecyclerView.ViewHolder(mView)
|
||||
}
|
||||
|
@ -23,11 +23,13 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kodein.di.DIAware
|
||||
import org.kodein.di.instance
|
||||
import java.util.*
|
||||
import java.util.Timer
|
||||
import kotlin.concurrent.schedule
|
||||
|
||||
class LoadingWorker(val context: Context, params: WorkerParameters) :
|
||||
Worker(context, params),
|
||||
class LoadingWorker(
|
||||
val context: Context,
|
||||
params: WorkerParameters,
|
||||
) : Worker(context, params),
|
||||
DIAware {
|
||||
override val di by lazy { (applicationContext as MyApp).di }
|
||||
private val repository: Repository by instance()
|
||||
@ -40,12 +42,13 @@ class LoadingWorker(val context: Context, params: WorkerParameters) :
|
||||
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
val notification =
|
||||
NotificationCompat.Builder(applicationContext, AppSettingsService.syncChannelId)
|
||||
NotificationCompat
|
||||
.Builder(applicationContext, AppSettingsService.SYNC_CHANNEL_ID)
|
||||
.setContentTitle(context.getString(R.string.loading_notification_title))
|
||||
.setContentText(context.getString(R.string.loading_notification_text))
|
||||
.setOngoing(true)
|
||||
.setPriority(PRIORITY_LOW)
|
||||
.setChannelId(AppSettingsService.syncChannelId)
|
||||
.setChannelId(AppSettingsService.SYNC_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
|
||||
|
||||
notificationManager.notify(1, notification.build())
|
||||
@ -87,19 +90,18 @@ class LoadingWorker(val context: Context, params: WorkerParameters) :
|
||||
PendingIntent.getActivity(context, 0, intent, pflags)
|
||||
|
||||
val newItemsNotification =
|
||||
NotificationCompat.Builder(
|
||||
applicationContext,
|
||||
AppSettingsService.newItemsChannelId,
|
||||
)
|
||||
.setContentTitle(context.getString(R.string.new_items_notification_title))
|
||||
NotificationCompat
|
||||
.Builder(
|
||||
applicationContext,
|
||||
AppSettingsService.NEW_ITEMS_CHANNEL,
|
||||
).setContentTitle(context.getString(R.string.new_items_notification_title))
|
||||
.setContentText(
|
||||
context.getString(
|
||||
R.string.new_items_notification_text,
|
||||
newSize,
|
||||
),
|
||||
)
|
||||
.setPriority(PRIORITY_DEFAULT)
|
||||
.setChannelId(AppSettingsService.newItemsChannelId)
|
||||
).setPriority(PRIORITY_DEFAULT)
|
||||
.setChannelId(AppSettingsService.NEW_ITEMS_CHANNEL)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
@ -66,7 +66,9 @@ import java.util.concurrent.ExecutionException
|
||||
|
||||
private const val IMAGE_JPG = "image/jpg"
|
||||
|
||||
class ArticleFragment : Fragment(), DIAware {
|
||||
class ArticleFragment :
|
||||
Fragment(),
|
||||
DIAware {
|
||||
private var fontSize: Int = 16
|
||||
private lateinit var item: SelfossModel.Item
|
||||
private lateinit var url: String
|
||||
@ -115,12 +117,13 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
contentText = item.content
|
||||
contentTitle = item.title.getHtmlDecoded()
|
||||
contentImage = item.getThumbnail(repository.baseUrl)
|
||||
contentSource = try {
|
||||
item.sourceAuthorAndDate()
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("Article Fragment parse date")
|
||||
item.sourceAuthorOnly()
|
||||
}
|
||||
contentSource =
|
||||
try {
|
||||
item.sourceAuthorAndDate()
|
||||
} catch (e: Exception) {
|
||||
e.sendSilentlyWithAcraWithName("Article Fragment parse date")
|
||||
item.sourceAuthorOnly()
|
||||
}
|
||||
allImages = item.getImages()
|
||||
|
||||
fontSize = appSettingsService.getFontSize()
|
||||
@ -166,7 +169,8 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
} catch (e: InflateException) {
|
||||
e.sendSilentlyWithAcraWithName("webview not available")
|
||||
try {
|
||||
AlertDialog.Builder(requireContext())
|
||||
AlertDialog
|
||||
.Builder(requireContext())
|
||||
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
||||
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
||||
.setPositiveButton(
|
||||
@ -174,8 +178,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
) { _, _ ->
|
||||
appSettingsService.disableArticleViewer()
|
||||
requireActivity().finish()
|
||||
}
|
||||
.create()
|
||||
}.create()
|
||||
.show()
|
||||
} catch (e: IllegalStateException) {
|
||||
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||
@ -234,21 +237,23 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
repository.markAsRead(this@ArticleFragment.item)
|
||||
}
|
||||
this@ArticleFragment.item.unread = false
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.marked_as_read,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
requireContext(),
|
||||
R.string.marked_as_read,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.unmarkAsRead(this@ArticleFragment.item)
|
||||
}
|
||||
this@ArticleFragment.item.unread = true
|
||||
Toast.makeText(
|
||||
context,
|
||||
R.string.marked_as_unread,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
R.string.marked_as_unread,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
}
|
||||
} catch (e: IllegalStateException) {
|
||||
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||
@ -321,8 +326,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
.asBitmap()
|
||||
.load(
|
||||
lead_image_url,
|
||||
)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
).apply(RequestOptions.fitCenterTransform())
|
||||
.into(binding.imageView)
|
||||
} else {
|
||||
binding.imageView.visibility = View.GONE
|
||||
@ -336,14 +340,16 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
override fun shouldOverrideUrlLoading(
|
||||
view: WebView?,
|
||||
url: String,
|
||||
): Boolean {
|
||||
return if (context != null && url.isUrlValid() && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
||||
): Boolean =
|
||||
if (context != null &&
|
||||
url.isUrlValid() &&
|
||||
binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
||||
) {
|
||||
requireContext().openUrlInBrowser(url)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun shouldInterceptRequest(
|
||||
@ -352,12 +358,18 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
): WebResourceResponse? {
|
||||
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||
if (url.lowercase(Locale.US).contains(".jpg") ||
|
||||
url.lowercase(Locale.US)
|
||||
url
|
||||
.lowercase(Locale.US)
|
||||
.contains(".jpeg")
|
||||
) {
|
||||
try {
|
||||
val image =
|
||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
||||
Glide
|
||||
.with(view)
|
||||
.asBitmap()
|
||||
.apply(glideOptions)
|
||||
.load(url)
|
||||
.submit()
|
||||
.get()
|
||||
return WebResourceResponse(
|
||||
IMAGE_JPG,
|
||||
@ -370,7 +382,12 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
} else if (url.lowercase(Locale.US).contains(".png")) {
|
||||
try {
|
||||
val image =
|
||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
||||
Glide
|
||||
.with(view)
|
||||
.asBitmap()
|
||||
.apply(glideOptions)
|
||||
.load(url)
|
||||
.submit()
|
||||
.get()
|
||||
return WebResourceResponse(
|
||||
IMAGE_JPG,
|
||||
@ -383,7 +400,12 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
||||
try {
|
||||
val image =
|
||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
||||
Glide
|
||||
.with(view)
|
||||
.asBitmap()
|
||||
.apply(glideOptions)
|
||||
.load(url)
|
||||
.submit()
|
||||
.get()
|
||||
return WebResourceResponse(
|
||||
IMAGE_JPG,
|
||||
@ -422,7 +444,6 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
context.theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
|
||||
|
||||
context.theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
|
||||
|
||||
} catch (e: IllegalStateException) {
|
||||
e.sendSilentlyWithAcraWithName("Context issue when setting attributes, but context wasn't null before")
|
||||
}
|
||||
@ -450,15 +471,13 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
GestureDetector(
|
||||
activity,
|
||||
object : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||
return performClick()
|
||||
}
|
||||
override fun onSingleTapUp(e: MotionEvent): Boolean = performClick()
|
||||
},
|
||||
)
|
||||
|
||||
binding.webcontent.setOnTouchListener { _, event ->
|
||||
gestureDetector.onTouchEvent(
|
||||
event
|
||||
event,
|
||||
)
|
||||
}
|
||||
|
||||
@ -597,10 +616,11 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
}
|
||||
|
||||
fun performClick(): Boolean {
|
||||
if (allImages != null && (
|
||||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
|
||||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
||||
)
|
||||
if (allImages != null &&
|
||||
(
|
||||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
|
||||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
||||
)
|
||||
) {
|
||||
val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
|
||||
|
||||
@ -612,4 +632,4 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,9 @@ import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.x.closestDI
|
||||
import org.kodein.di.instance
|
||||
|
||||
class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
||||
class FilterSheetFragment :
|
||||
BottomSheetDialogFragment(),
|
||||
DIAware {
|
||||
private lateinit var binding: FilterFragmentBinding
|
||||
override val di: DI by closestDI()
|
||||
private val repository: Repository by instance()
|
||||
@ -80,7 +82,8 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
||||
val c = Chip(context)
|
||||
c.ellipsize = TextUtils.TruncateAt.END
|
||||
|
||||
Glide.with(context)
|
||||
Glide
|
||||
.with(context)
|
||||
.load(source.getIcon(repository.baseUrl))
|
||||
.into(
|
||||
object : ViewTarget<Chip?, Drawable?>(c) {
|
||||
@ -190,4 +193,4 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
||||
companion object {
|
||||
const val TAG = "FilterModalBottomSheet"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ class ImageFragment : Fragment() {
|
||||
private lateinit var imageUrl: String
|
||||
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||
private var _binding: FragmentImageBinding? = null
|
||||
private val binding get() = _binding
|
||||
val binding get() = _binding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -31,7 +31,8 @@ class ImageFragment : Fragment() {
|
||||
val view = binding?.root
|
||||
|
||||
binding!!.photoView.visibility = View.VISIBLE
|
||||
Glide.with(requireActivity())
|
||||
Glide
|
||||
.with(requireActivity())
|
||||
.asBitmap()
|
||||
.apply(glideOptions)
|
||||
.load(imageUrl)
|
||||
|
@ -17,9 +17,12 @@ fun SelfossModel.Item.preloadImages(context: Context): Boolean {
|
||||
try {
|
||||
for (url in imageUrls) {
|
||||
if (URLUtil.isValidUrl(url)) {
|
||||
Glide.with(context).asBitmap()
|
||||
Glide
|
||||
.with(context)
|
||||
.asBitmap()
|
||||
.apply(glideOptions)
|
||||
.load(url).submit()
|
||||
.load(url)
|
||||
.submit()
|
||||
}
|
||||
}
|
||||
} catch (e: Error) {
|
||||
@ -40,4 +43,4 @@ fun String.toTextDrawableString(): String {
|
||||
}
|
||||
}
|
||||
return textDrawable.toString()
|
||||
}
|
||||
}
|
||||
|
@ -64,15 +64,14 @@ class SettingsActivity :
|
||||
outState.putCharSequence(TITLE_TAG, title)
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
return if (supportFragmentManager.popBackStackImmediate()) {
|
||||
override fun onSupportNavigateUp(): Boolean =
|
||||
if (supportFragmentManager.popBackStackImmediate()) {
|
||||
supportActionBar?.title = getText(R.string.title_activity_settings)
|
||||
false
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPreferenceStartFragment(
|
||||
caller: PreferenceFragmentCompat,
|
||||
@ -81,15 +80,17 @@ class SettingsActivity :
|
||||
// Instantiate the new Fragment
|
||||
val args = pref.extras
|
||||
val fragment =
|
||||
supportFragmentManager.fragmentFactory.instantiate(
|
||||
classLoader,
|
||||
pref.fragment.toString(),
|
||||
).apply {
|
||||
arguments = args
|
||||
setTargetFragment(caller, 0)
|
||||
}
|
||||
supportFragmentManager.fragmentFactory
|
||||
.instantiate(
|
||||
classLoader,
|
||||
pref.fragment.toString(),
|
||||
).apply {
|
||||
arguments = args
|
||||
setTargetFragment(caller, 0)
|
||||
}
|
||||
// Replace the existing Fragment with the new Fragment
|
||||
supportFragmentManager.beginTransaction()
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings, fragment)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
@ -108,7 +109,7 @@ class SettingsActivity :
|
||||
preferenceManager.findPreference<Preference>(CURRENT_THEME)?.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _, newValue ->
|
||||
AppCompatDelegate.setDefaultNightMode(
|
||||
newValue.toString().toInt()
|
||||
newValue.toString().toInt(),
|
||||
) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
|
||||
true
|
||||
}
|
||||
@ -144,11 +145,12 @@ class SettingsActivity :
|
||||
val input: Int = (dest.toString() + source.toString()).toInt()
|
||||
if (input in 1..200) return@InputFilter null
|
||||
} catch (nfe: NumberFormatException) {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
R.string.items_number_should_be_number,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
activity,
|
||||
R.string.items_number_should_be_number,
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
}
|
||||
""
|
||||
},
|
||||
@ -234,19 +236,19 @@ class SettingsActivity :
|
||||
|
||||
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
openUrl(AppSettingsService.trackerUrl)
|
||||
openUrl(AppSettingsService.BUG_URL)
|
||||
true
|
||||
}
|
||||
|
||||
preferenceManager.findPreference<Preference>("sourceLink")?.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
openUrl(AppSettingsService.sourceUrl)
|
||||
openUrl(AppSettingsService.SOURCE_URL)
|
||||
false
|
||||
}
|
||||
|
||||
preferenceManager.findPreference<Preference>("translation")?.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
openUrl(AppSettingsService.translationUrl)
|
||||
openUrl(AppSettingsService.TRANSLATION_URL)
|
||||
false
|
||||
}
|
||||
}
|
||||
@ -260,4 +262,4 @@ class SettingsActivity :
|
||||
setPreferencesFromResource(R.xml.pref_experimental, rootKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package bou.amine.apps.readerforselfossv2.android.testing
|
||||
import androidx.test.espresso.idling.CountingIdlingResource
|
||||
|
||||
object CountingIdlingResourceSingleton {
|
||||
|
||||
private const val RESOURCE = "GLOBAL"
|
||||
|
||||
@JvmField
|
||||
@ -18,4 +17,4 @@ object CountingIdlingResourceSingleton {
|
||||
countingIdlingResource.decrement()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package bou.amine.apps.readerforselfossv2.android.testing
|
||||
|
||||
import android.os.Build
|
||||
|
||||
|
||||
class TestingHelper {
|
||||
fun isUnitTest(): Boolean {
|
||||
var device = Build.DEVICE
|
||||
@ -16,4 +15,4 @@ class TestingHelper {
|
||||
}
|
||||
return device == "robolectric" && product == "robolectric"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,10 @@ fun Context.shareLink(
|
||||
sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle)
|
||||
sendIntent.type = "text/plain"
|
||||
startActivity(
|
||||
Intent.createChooser(
|
||||
sendIntent,
|
||||
getString(R.string.share),
|
||||
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
Intent
|
||||
.createChooser(
|
||||
sendIntent,
|
||||
getString(R.string.share),
|
||||
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
)
|
||||
}
|
||||
|
@ -59,7 +59,5 @@ class CircleImageView
|
||||
textView.text = text.toTextDrawableString()
|
||||
}
|
||||
|
||||
private fun colorFromIdentifier(key: String): Int {
|
||||
return colorScheme[abs(key.hashCode()) % colorScheme.size]
|
||||
}
|
||||
private fun colorFromIdentifier(key: String): Int = colorScheme[abs(key.hashCode()) % colorScheme.size]
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
|
||||
|
||||
fun Context.openItemUrl(
|
||||
currentItem: Int,
|
||||
linkDecoded: String,
|
||||
@ -26,11 +25,12 @@ fun Context.openItemUrl(
|
||||
app: Activity,
|
||||
) {
|
||||
if (!linkDecoded.isUrlValid()) {
|
||||
Toast.makeText(
|
||||
this,
|
||||
this.getString(R.string.cant_open_invalid_url),
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
Toast
|
||||
.makeText(
|
||||
this,
|
||||
this.getString(R.string.cant_open_invalid_url),
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
} else {
|
||||
if (articleViewer) {
|
||||
val intent = Intent(this, ReaderActivity::class.java)
|
||||
@ -42,8 +42,7 @@ fun Context.openItemUrl(
|
||||
}
|
||||
}
|
||||
|
||||
fun String.isUrlValid(): Boolean =
|
||||
this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
|
||||
fun String.isUrlValid(): Boolean = this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
|
||||
|
||||
fun String.isBaseUrlInvalid(): Boolean {
|
||||
val baseUrl = this.toHttpUrlOrNull()
|
||||
@ -61,7 +60,6 @@ fun Context.openItemUrlInBrowserAsNewTask(i: SelfossModel.Item) {
|
||||
}
|
||||
|
||||
fun Context.openUrlInBrowserAsNewTask(url: String) {
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
intent.data = Uri.parse(url)
|
||||
@ -80,7 +78,6 @@ fun Context.mayBeStartActivity(intent: Intent) {
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(this, getString(R.string.no_browser), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class LinkOnTouchListener : View.OnTouchListener {
|
||||
@ -122,4 +119,4 @@ class LinkOnTouchListener : View.OnTouchListener {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,4 +6,4 @@ import org.acra.ktx.sendSilentlyWithAcra
|
||||
fun Throwable.sendSilentlyWithAcraWithName(name: String) {
|
||||
ACRA.errorReporter.putCustomData("error_source", name)
|
||||
this.sendSilentlyWithAcra()
|
||||
}
|
||||
}
|
||||
|
@ -8,22 +8,19 @@ import org.acra.config.CoreConfiguration
|
||||
import org.acra.config.ReportingAdministrator
|
||||
import org.acra.data.CrashReportData
|
||||
|
||||
|
||||
@AutoService(ReportingAdministrator::class)
|
||||
class AcraReportingAdministrator : ReportingAdministrator {
|
||||
override fun shouldStartCollecting(
|
||||
context: Context,
|
||||
config: CoreConfiguration,
|
||||
reportBuilder: ReportBuilder
|
||||
): Boolean {
|
||||
return reportBuilder.exception !is DeadSystemException && (reportBuilder.exception != null && reportBuilder.exception!!::class.simpleName != "CannotDeliverBroadcastException")
|
||||
}
|
||||
reportBuilder: ReportBuilder,
|
||||
): Boolean =
|
||||
reportBuilder.exception !is DeadSystemException &&
|
||||
(reportBuilder.exception != null && reportBuilder.exception!!::class.simpleName != "CannotDeliverBroadcastException")
|
||||
|
||||
override fun shouldSendReport(
|
||||
context: Context,
|
||||
config: CoreConfiguration,
|
||||
crashReportData: CrashReportData
|
||||
): Boolean {
|
||||
return crashReportData.get("BRAND") != "redroid"
|
||||
}
|
||||
}
|
||||
crashReportData: CrashReportData,
|
||||
): Boolean = crashReportData.get("BRAND") != "redroid"
|
||||
}
|
||||
|
@ -13,7 +13,8 @@ import java.io.InputStream
|
||||
fun Context.bitmapCenterCrop(
|
||||
url: String,
|
||||
iv: ImageView,
|
||||
) = Glide.with(this)
|
||||
) = Glide
|
||||
.with(this)
|
||||
.asBitmap()
|
||||
.load(url)
|
||||
.apply(RequestOptions.centerCropTransform())
|
||||
@ -25,7 +26,8 @@ fun Context.circularDrawable(
|
||||
) {
|
||||
view.textView.text = ""
|
||||
|
||||
Glide.with(this)
|
||||
Glide
|
||||
.with(this)
|
||||
.load(url)
|
||||
.into(view.imageView)
|
||||
}
|
||||
|
@ -7,7 +7,9 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AppViewModel(private val repository: Repository) : ViewModel() {
|
||||
class AppViewModel(
|
||||
private val repository: Repository,
|
||||
) : ViewModel() {
|
||||
private val _networkAvailableProvider = MutableSharedFlow<Boolean>()
|
||||
val networkAvailableProvider = _networkAvailableProvider.asSharedFlow()
|
||||
private var wasConnected = true
|
||||
@ -19,11 +21,10 @@ class AppViewModel(private val repository: Repository) : ViewModel() {
|
||||
if (isConnected && !wasConnected && repository.connectionMonitored) {
|
||||
_networkAvailableProvider.emit(true)
|
||||
wasConnected = true
|
||||
} else if (!isConnected && wasConnected && repository.connectionMonitored)
|
||||
{
|
||||
_networkAvailableProvider.emit(false)
|
||||
wasConnected = false
|
||||
}
|
||||
} else if (!isConnected && wasConnected && repository.connectionMonitored) {
|
||||
_networkAvailableProvider.emit(false)
|
||||
wasConnected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,13 +11,17 @@ fun dialogMessage(): String {
|
||||
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)
|
||||
val item = this.findItem(id)
|
||||
assertTrue(item.isEnabled)
|
||||
}
|
||||
|
||||
fun Menu.assertVisible(@IdRes id: Int) {
|
||||
fun Menu.assertVisible(
|
||||
@IdRes id: Int,
|
||||
) {
|
||||
val item = this.findItem(id)
|
||||
assertTrue(item.isVisible)
|
||||
}
|
||||
}
|
@ -11,10 +11,8 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
|
||||
|
||||
@RunWith(RobotElectriqueRunnerclass::class)
|
||||
@RunWith(RobotElectriqueRunner::class)
|
||||
class LoginActivityTest {
|
||||
|
||||
@Test
|
||||
fun login_shouldDisplay() {
|
||||
Robolectric.buildActivity(LoginActivity::class.java).use { controller ->
|
||||
@ -74,4 +72,4 @@ class LoginActivityTest {
|
||||
assertEquals(expectedIntent.component, actual.component)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,8 @@ package bou.amine.apps.readerforselfossv2.android.tests.robolectric
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
class RobotElectriqueRunnerclass(testClass: Class<*>?) :
|
||||
RobolectricTestRunner(testClass) {
|
||||
|
||||
override fun buildGlobalConfig(): Config {
|
||||
return Config.Builder().setSdk(25, 30, 33).build()
|
||||
}
|
||||
}
|
||||
class RobotElectriqueRunner(
|
||||
testClass: Class<*>?,
|
||||
) : RobolectricTestRunner(testClass) {
|
||||
override fun buildGlobalConfig(): Config = Config.Builder().setSdk(25, 30, 33).build()
|
||||
}
|
||||
|
@ -42,14 +42,14 @@ private const val FEED_URL = "https://test.com/feed"
|
||||
|
||||
private const val TAGS = "Test, New"
|
||||
|
||||
private val NUMBER_ARTICLES = 100
|
||||
private val NUMBER_UNREAD = 50
|
||||
private val NUMBER_STARRED = 20
|
||||
|
||||
class RepositoryTest {
|
||||
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
||||
private val appSettingsService = mockk<AppSettingsService>()
|
||||
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 fun initializeRepository(
|
||||
@ -75,19 +75,20 @@ class RepositoryTest {
|
||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||
|
||||
coEvery { api.apiInformation() } returns
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = SelfossModel.ApiInformation(
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data =
|
||||
SelfossModel.ApiInformation(
|
||||
"2.19-ba1e8e3",
|
||||
"4.0.0",
|
||||
SelfossModel.ApiConfiguration(false, true)
|
||||
SelfossModel.ApiConfiguration(false, true),
|
||||
),
|
||||
)
|
||||
)
|
||||
coEvery { api.stats() } returns
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED),
|
||||
)
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED),
|
||||
)
|
||||
|
||||
every { db.itemsQueries.deleteItemsWhereSource(any()) } returns Unit
|
||||
every { db.itemsQueries.items().executeAsList() } returns generateTestDBItems()
|
||||
@ -117,7 +118,7 @@ class RepositoryTest {
|
||||
fun get_api_4_date_with_api_1_version_stored() {
|
||||
every { appSettingsService.getApiVersion() } returns 1
|
||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
every { appSettingsService.updateApiVersion(any()) } returns Unit
|
||||
|
||||
initializeRepository()
|
||||
@ -133,14 +134,15 @@ class RepositoryTest {
|
||||
fun get_public_access() {
|
||||
every { appSettingsService.updatePublicAccess(any()) } returns Unit
|
||||
coEvery { api.apiInformation() } returns
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = SelfossModel.ApiInformation(
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data =
|
||||
SelfossModel.ApiInformation(
|
||||
"2.19-ba1e8e3",
|
||||
"4.0.0",
|
||||
SelfossModel.ApiConfiguration(true, true)
|
||||
SelfossModel.ApiConfiguration(true, true),
|
||||
),
|
||||
)
|
||||
)
|
||||
every { appSettingsService.getUserName() } returns ""
|
||||
|
||||
initializeRepository()
|
||||
@ -153,14 +155,15 @@ class RepositoryTest {
|
||||
fun get_public_access_username_not_empty() {
|
||||
every { appSettingsService.updatePublicAccess(any()) } returns Unit
|
||||
coEvery { api.apiInformation() } returns
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = SelfossModel.ApiInformation(
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data =
|
||||
SelfossModel.ApiInformation(
|
||||
"2.19-ba1e8e3",
|
||||
"4.0.0",
|
||||
SelfossModel.ApiConfiguration(true, true)
|
||||
SelfossModel.ApiConfiguration(true, true),
|
||||
),
|
||||
)
|
||||
)
|
||||
every { appSettingsService.getUserName() } returns "username"
|
||||
|
||||
initializeRepository()
|
||||
@ -173,14 +176,15 @@ class RepositoryTest {
|
||||
fun get_public_access_no_auth() {
|
||||
every { appSettingsService.updatePublicAccess(any()) } returns Unit
|
||||
coEvery { api.apiInformation() } returns
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = SelfossModel.ApiInformation(
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data =
|
||||
SelfossModel.ApiInformation(
|
||||
"2.19-ba1e8e3",
|
||||
"4.0.0",
|
||||
SelfossModel.ApiConfiguration(true, false)
|
||||
SelfossModel.ApiConfiguration(true, false),
|
||||
),
|
||||
)
|
||||
)
|
||||
every { appSettingsService.getUserName() } returns ""
|
||||
|
||||
initializeRepository()
|
||||
@ -193,14 +197,15 @@ class RepositoryTest {
|
||||
fun get_public_access_disabled() {
|
||||
every { appSettingsService.updatePublicAccess(any()) } returns Unit
|
||||
coEvery { api.apiInformation() } returns
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = SelfossModel.ApiInformation(
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data =
|
||||
SelfossModel.ApiInformation(
|
||||
"2.19-ba1e8e3",
|
||||
"4.0.0",
|
||||
SelfossModel.ApiConfiguration(false, true)
|
||||
SelfossModel.ApiConfiguration(false, true),
|
||||
),
|
||||
)
|
||||
)
|
||||
every { appSettingsService.getUserName() } returns ""
|
||||
|
||||
initializeRepository()
|
||||
@ -216,10 +221,10 @@ class RepositoryTest {
|
||||
val itemParameters = FakeItemParameters()
|
||||
itemParameters.datetime = "2021-04-23 11:45:32"
|
||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = generateTestApiItem(itemParameters),
|
||||
)
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = generateTestApiItem(itemParameters),
|
||||
)
|
||||
|
||||
initializeRepository()
|
||||
runBlocking {
|
||||
@ -232,7 +237,7 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun get_newer_items() {
|
||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
|
||||
initializeRepository()
|
||||
runBlocking {
|
||||
@ -247,7 +252,7 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun get_all_newer_items() {
|
||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
|
||||
initializeRepository()
|
||||
repository.displayedItems = ItemType.ALL
|
||||
@ -263,7 +268,7 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun get_newer_starred_items() {
|
||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
|
||||
initializeRepository()
|
||||
repository.displayedItems = ItemType.STARRED
|
||||
@ -302,8 +307,8 @@ class RepositoryTest {
|
||||
coEvery { db.itemsQueries.items().executeAsList() } returns generateTestDBItems(
|
||||
itemParameter1,
|
||||
) +
|
||||
generateTestDBItems(itemParameter2) +
|
||||
generateTestDBItems(itemParameter3)
|
||||
generateTestDBItems(itemParameter2) +
|
||||
generateTestDBItems(itemParameter3)
|
||||
|
||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||
|
||||
@ -330,8 +335,8 @@ class RepositoryTest {
|
||||
coEvery { db.itemsQueries.items().executeAsList() } returns generateTestDBItems(
|
||||
itemParameter1,
|
||||
) +
|
||||
generateTestDBItems(itemParameter2) +
|
||||
generateTestDBItems(itemParameter3)
|
||||
generateTestDBItems(itemParameter2) +
|
||||
generateTestDBItems(itemParameter3)
|
||||
|
||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||
|
||||
@ -360,7 +365,7 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun get_older_items() {
|
||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
|
||||
initializeRepository()
|
||||
repository.items = ArrayList(generateTestApiItem())
|
||||
@ -376,7 +381,7 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun get_all_older_items() {
|
||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
|
||||
initializeRepository()
|
||||
repository.items = ArrayList(generateTestApiItem())
|
||||
@ -393,7 +398,7 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun get_older_starred_items() {
|
||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
StatusAndData(success = true, data = generateTestApiItem())
|
||||
|
||||
initializeRepository()
|
||||
repository.displayedItems = ItemType.STARRED
|
||||
@ -833,7 +838,7 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun create_source() {
|
||||
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
|
||||
SuccessResponse(true)
|
||||
SuccessResponse(true)
|
||||
|
||||
initializeRepository()
|
||||
var response: Boolean
|
||||
@ -861,7 +866,7 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun create_source_but_response_fails() {
|
||||
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
|
||||
SuccessResponse(false)
|
||||
SuccessResponse(false)
|
||||
|
||||
initializeRepository()
|
||||
var response: Boolean
|
||||
@ -889,7 +894,7 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun create_source_without_connection() {
|
||||
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
|
||||
SuccessResponse(true)
|
||||
SuccessResponse(true)
|
||||
|
||||
initializeRepository(MutableStateFlow(false))
|
||||
var response: Boolean
|
||||
@ -962,10 +967,10 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun update_remote() {
|
||||
coEvery { api.update() } returns
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = "finished",
|
||||
)
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = "finished",
|
||||
)
|
||||
|
||||
initializeRepository()
|
||||
var response: Boolean
|
||||
@ -980,10 +985,10 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun update_remote_but_response_fails() {
|
||||
coEvery { api.update() } returns
|
||||
StatusAndData(
|
||||
success = false,
|
||||
data = "unallowed access",
|
||||
)
|
||||
StatusAndData(
|
||||
success = false,
|
||||
data = "unallowed access",
|
||||
)
|
||||
|
||||
initializeRepository()
|
||||
var response: Boolean
|
||||
@ -998,10 +1003,10 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun update_remote_with_unallowed_access() {
|
||||
coEvery { api.update() } returns
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = "unallowed access",
|
||||
)
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = "unallowed access",
|
||||
)
|
||||
|
||||
initializeRepository()
|
||||
var response: Boolean
|
||||
@ -1016,10 +1021,10 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun update_remote_without_connection() {
|
||||
coEvery { api.update() } returns
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = "undocumented...",
|
||||
)
|
||||
StatusAndData(
|
||||
success = true,
|
||||
data = "undocumented...",
|
||||
)
|
||||
|
||||
initializeRepository(MutableStateFlow(false))
|
||||
var response: Boolean
|
||||
@ -1109,11 +1114,11 @@ class RepositoryTest {
|
||||
any(),
|
||||
)
|
||||
} returnsMany
|
||||
listOf(
|
||||
StatusAndData(success = true, data = generateTestApiItem(itemParameter1)),
|
||||
StatusAndData(success = true, data = generateTestApiItem(itemParameter2)),
|
||||
StatusAndData(success = true, data = generateTestApiItem(itemParameter1)),
|
||||
)
|
||||
listOf(
|
||||
StatusAndData(success = true, data = generateTestApiItem(itemParameter1)),
|
||||
StatusAndData(success = true, data = generateTestApiItem(itemParameter2)),
|
||||
StatusAndData(success = true, data = generateTestApiItem(itemParameter1)),
|
||||
)
|
||||
|
||||
initializeRepository()
|
||||
prepareSearch()
|
||||
@ -1127,7 +1132,7 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun cache_items_but_response_fails() {
|
||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||
StatusAndData(success = false, data = generateTestApiItem())
|
||||
StatusAndData(success = false, data = generateTestApiItem())
|
||||
|
||||
initializeRepository()
|
||||
prepareSearch()
|
||||
@ -1141,7 +1146,7 @@ class RepositoryTest {
|
||||
@Test
|
||||
fun cache_items_without_connection() {
|
||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||
StatusAndData(success = false, data = generateTestApiItem())
|
||||
StatusAndData(success = false, data = generateTestApiItem())
|
||||
|
||||
initializeRepository(MutableStateFlow(false))
|
||||
prepareSearch()
|
||||
@ -1168,4 +1173,4 @@ class RepositoryTest {
|
||||
)
|
||||
repository.searchFilter = "search"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ package bou.amine.apps.readerforselfossv2.tests.repository
|
||||
import bou.amine.apps.readerforselfossv2.dao.ITEM
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
|
||||
fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> {
|
||||
return listOf(
|
||||
fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> =
|
||||
listOf(
|
||||
ITEM(
|
||||
id = item.id,
|
||||
datetime = item.datetime,
|
||||
@ -20,10 +20,9 @@ fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<I
|
||||
author = item.author,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<SelfossModel.Item> {
|
||||
return listOf(
|
||||
fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<SelfossModel.Item> =
|
||||
listOf(
|
||||
SelfossModel.Item(
|
||||
id = item.id.toInt(),
|
||||
datetime = item.datetime,
|
||||
@ -39,7 +38,6 @@ fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<S
|
||||
author = item.author,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
class FakeItemParameters {
|
||||
var id = "20"
|
||||
@ -56,4 +54,4 @@ class FakeItemParameters {
|
||||
var sourcetitle = "La Chimica e la Società"
|
||||
var tags = "Chimica, Testing"
|
||||
var author = "Someone important"
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,13 @@ import android.content.Context
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
||||
|
||||
actual class DriverFactory(private val context: Context) {
|
||||
actual fun createDriver(): SqlDriver {
|
||||
return AndroidSqliteDriver(
|
||||
actual class DriverFactory(
|
||||
private val context: Context,
|
||||
) {
|
||||
actual fun createDriver(): SqlDriver =
|
||||
AndroidSqliteDriver(
|
||||
ReaderForSelfossDB.Schema,
|
||||
context,
|
||||
"ReaderForSelfossV2-android.db"
|
||||
"ReaderForSelfossV2-android.db",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -8,16 +8,18 @@ class NaiveTrustManager : X509TrustManager {
|
||||
override fun checkClientTrusted(
|
||||
chain: Array<out X509Certificate>?,
|
||||
authType: String?,
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
override fun checkServerTrusted(
|
||||
chain: Array<out X509Certificate>?,
|
||||
authType: String?,
|
||||
) {}
|
||||
) {
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
|
||||
}
|
||||
|
||||
actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) {
|
||||
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
||||
config.https.trustManager = NaiveTrustManager()
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import io.github.aakira.napier.Napier
|
||||
import kotlinx.datetime.*
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
actual class DateUtils {
|
||||
actual companion object {
|
||||
|
@ -4,19 +4,13 @@ import android.net.Uri
|
||||
import android.text.Html
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import org.jsoup.Jsoup
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
actual fun String.getHtmlDecoded(): String {
|
||||
return Html.fromHtml(this).toString()
|
||||
}
|
||||
actual fun String.getHtmlDecoded(): String = Html.fromHtml(this).toString()
|
||||
|
||||
actual fun SelfossModel.Item.getIcon(baseUrl: String): String {
|
||||
return constructUrl(baseUrl, "favicons", icon)
|
||||
}
|
||||
actual fun SelfossModel.Item.getIcon(baseUrl: String): String = constructUrl(baseUrl, "favicons", icon)
|
||||
|
||||
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String {
|
||||
return constructUrl(baseUrl, "thumbnails", thumbnail)
|
||||
}
|
||||
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String = constructUrl(baseUrl, "thumbnails", thumbnail)
|
||||
|
||||
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
||||
val allImages = ArrayList<String>()
|
||||
@ -34,16 +28,14 @@ actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
||||
return allImages
|
||||
}
|
||||
|
||||
actual fun SelfossModel.Source.getIcon(baseUrl: String): String {
|
||||
return constructUrl(baseUrl, "favicons", icon)
|
||||
}
|
||||
actual fun SelfossModel.Source.getIcon(baseUrl: String): String = constructUrl(baseUrl, "favicons", icon)
|
||||
|
||||
actual fun constructUrl(
|
||||
baseUrl: String,
|
||||
path: String,
|
||||
file: String?,
|
||||
): String {
|
||||
return if (file == null || file == "null" || file.isEmpty()) {
|
||||
): String =
|
||||
if (file == null || file == "null" || file.isEmpty()) {
|
||||
""
|
||||
} else {
|
||||
val baseUriBuilder = Uri.parse(baseUrl).buildUpon()
|
||||
@ -51,4 +43,3 @@ actual fun constructUrl(
|
||||
|
||||
baseUriBuilder.toString()
|
||||
}
|
||||
}
|
||||
|
@ -4,4 +4,4 @@ import app.cash.sqldelight.db.SqlDriver
|
||||
|
||||
expect class DriverFactory {
|
||||
fun createDriver(): SqlDriver
|
||||
}
|
||||
}
|
@ -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.SelfossApi
|
@ -3,19 +3,20 @@ package bou.amine.apps.readerforselfossv2.model
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class SuccessResponse(val success: Boolean) {
|
||||
class SuccessResponse(
|
||||
val success: Boolean,
|
||||
) {
|
||||
val isSuccess: Boolean
|
||||
get() = success
|
||||
}
|
||||
|
||||
class StatusAndData<T>(val success: Boolean, val data: T? = null) {
|
||||
class StatusAndData<T>(
|
||||
val success: Boolean,
|
||||
val data: T? = null,
|
||||
) {
|
||||
companion object {
|
||||
fun <T> succes(d: T): StatusAndData<T> {
|
||||
return StatusAndData(true, d)
|
||||
}
|
||||
fun <T> succes(d: T): StatusAndData<T> = StatusAndData(true, d)
|
||||
|
||||
fun <T> error(): StatusAndData<T> {
|
||||
return StatusAndData(false)
|
||||
}
|
||||
fun <T> error(): StatusAndData<T> = StatusAndData(false)
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ class SelfossModel {
|
||||
}
|
||||
|
||||
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
|
||||
@ -172,12 +172,11 @@ class SelfossModel {
|
||||
|
||||
// TODO: this seems to be super slow.
|
||||
object TagsListSerializer : KSerializer<List<String>> {
|
||||
override fun deserialize(decoder: Decoder): List<String> {
|
||||
return when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {
|
||||
override fun deserialize(decoder: Decoder): List<String> =
|
||||
when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {
|
||||
is JsonArray -> json.toList().map { it.toString().replace("^\"|\"$".toRegex(), "") }
|
||||
else -> json.toString().split(",")
|
||||
}
|
||||
}
|
||||
|
||||
override val descriptor: SerialDescriptor
|
||||
get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
|
||||
@ -188,7 +187,7 @@ class SelfossModel {
|
||||
) {
|
||||
encoder.encodeCollection(
|
||||
PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING),
|
||||
value.size
|
||||
value.size,
|
||||
) { this.toString() }
|
||||
}
|
||||
}
|
||||
@ -204,10 +203,11 @@ class SelfossModel {
|
||||
}
|
||||
|
||||
override val descriptor: SerialDescriptor
|
||||
get() = PrimitiveSerialDescriptor(
|
||||
"BooleanOrIntForSomeSelfossVersions",
|
||||
PrimitiveKind.BOOLEAN
|
||||
)
|
||||
get() =
|
||||
PrimitiveSerialDescriptor(
|
||||
"BooleanOrIntForSomeSelfossVersions",
|
||||
PrimitiveKind.BOOLEAN,
|
||||
)
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
@ -216,4 +216,4 @@ class SelfossModel {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,21 @@
|
||||
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.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.*
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -170,8 +179,8 @@ class Repository(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSpouts(): Map<String, SelfossModel.Spout> {
|
||||
return if (isNetworkAvailable()) {
|
||||
suspend fun getSpouts(): Map<String, SelfossModel.Spout> =
|
||||
if (isNetworkAvailable()) {
|
||||
val spouts = api.spouts()
|
||||
if (spouts.success && spouts.data != null) {
|
||||
spouts.data
|
||||
@ -181,7 +190,6 @@ class Repository(
|
||||
} else {
|
||||
throw NetworkUnavailableException()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSourcesDetailsOrStats(): ArrayList<SelfossModel.Source> {
|
||||
var sources = ArrayList<SelfossModel.Source>()
|
||||
@ -234,14 +242,13 @@ class Repository(
|
||||
return success
|
||||
}
|
||||
|
||||
private suspend fun markAsReadById(id: Int): Boolean {
|
||||
return if (isNetworkAvailable()) {
|
||||
private suspend fun markAsReadById(id: Int): Boolean =
|
||||
if (isNetworkAvailable()) {
|
||||
api.markAsRead(id.toString()).isSuccess
|
||||
} else {
|
||||
insertDBAction(id.toString(), read = true)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
|
||||
val success = unmarkAsReadById(item.id)
|
||||
@ -252,14 +259,13 @@ class Repository(
|
||||
return success
|
||||
}
|
||||
|
||||
private suspend fun unmarkAsReadById(id: Int): Boolean {
|
||||
return if (isNetworkAvailable()) {
|
||||
private suspend fun unmarkAsReadById(id: Int): Boolean =
|
||||
if (isNetworkAvailable()) {
|
||||
api.unmarkAsRead(id.toString()).isSuccess
|
||||
} else {
|
||||
insertDBAction(id.toString(), unread = true)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun starr(item: SelfossModel.Item): Boolean {
|
||||
val success = starrById(item.id)
|
||||
@ -270,14 +276,13 @@ class Repository(
|
||||
return success
|
||||
}
|
||||
|
||||
private suspend fun starrById(id: Int): Boolean {
|
||||
return if (isNetworkAvailable()) {
|
||||
private suspend fun starrById(id: Int): Boolean =
|
||||
if (isNetworkAvailable()) {
|
||||
api.starr(id.toString()).isSuccess
|
||||
} else {
|
||||
insertDBAction(id.toString(), starred = true)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun unstarr(item: SelfossModel.Item): Boolean {
|
||||
val success = unstarrById(item.id)
|
||||
@ -288,14 +293,13 @@ class Repository(
|
||||
return success
|
||||
}
|
||||
|
||||
private suspend fun unstarrById(id: Int): Boolean {
|
||||
return if (isNetworkAvailable()) {
|
||||
private suspend fun unstarrById(id: Int): Boolean =
|
||||
if (isNetworkAvailable()) {
|
||||
api.unstarr(id.toString()).isSuccess
|
||||
} else {
|
||||
insertDBAction(id.toString(), starred = true)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
||||
var success = false
|
||||
@ -361,12 +365,13 @@ class Repository(
|
||||
): Boolean {
|
||||
var response = false
|
||||
if (isNetworkAvailable()) {
|
||||
response = api.createSourceForVersion(
|
||||
title,
|
||||
url,
|
||||
spout,
|
||||
tags,
|
||||
).isSuccess == true
|
||||
response = api
|
||||
.createSourceForVersion(
|
||||
title,
|
||||
url,
|
||||
spout,
|
||||
tags,
|
||||
).isSuccess == true
|
||||
}
|
||||
|
||||
return response
|
||||
@ -407,13 +412,12 @@ class Repository(
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun updateRemote(): Boolean {
|
||||
return if (isNetworkAvailable()) {
|
||||
suspend fun updateRemote(): Boolean =
|
||||
if (isNetworkAvailable()) {
|
||||
api.update().data.equals("finished")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun login(): Boolean {
|
||||
var result = false
|
||||
@ -422,7 +426,7 @@ class Repository(
|
||||
val response = api.login()
|
||||
result = response.isSuccess == true
|
||||
} catch (cause: Throwable) {
|
||||
Napier.e("login failed", cause, tag = "RepositoryImpl.login")
|
||||
Napier.e("login failed", cause, tag = "Repository.login")
|
||||
}
|
||||
}
|
||||
return result
|
||||
@ -436,7 +440,7 @@ class Repository(
|
||||
// a random rss feed, that would throw a NoTransformationFoundException
|
||||
fetchFailed = !api.getItemsWithoutCatch().success
|
||||
} 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 {
|
||||
val response = api.logout()
|
||||
if (!response.isSuccess) {
|
||||
Napier.e("Couldn't logout.", tag = "RepositoryImpl.logout")
|
||||
Napier.e("Couldn't logout.", tag = "Repository.logout")
|
||||
}
|
||||
} catch (cause: Throwable) {
|
||||
Napier.e("logout failed", cause, tag = "RepositoryImpl.logout")
|
||||
Napier.e("logout failed", cause, tag = "Repository.logout")
|
||||
}
|
||||
appSettingsService.clearAll()
|
||||
} else {
|
||||
@ -578,16 +582,19 @@ class Repository(
|
||||
markAsReadById(action.articleid.toInt()),
|
||||
action,
|
||||
)
|
||||
|
||||
action.unread ->
|
||||
doAndReportOnFail(
|
||||
unmarkAsReadById(action.articleid.toInt()),
|
||||
action,
|
||||
)
|
||||
|
||||
action.starred ->
|
||||
doAndReportOnFail(
|
||||
starrById(action.articleid.toInt()),
|
||||
action,
|
||||
)
|
||||
|
||||
action.unstarred ->
|
||||
doAndReportOnFail(
|
||||
unstarrById(action.articleid.toInt()),
|
||||
@ -618,9 +625,7 @@ class Repository(
|
||||
_readerItems = readerItems
|
||||
}
|
||||
|
||||
fun getReaderItems(): ArrayList<SelfossModel.Item> {
|
||||
return _readerItems
|
||||
}
|
||||
fun getReaderItems(): ArrayList<SelfossModel.Item> = _readerItems
|
||||
|
||||
fun migrate(driverFactory: DriverFactory) {
|
||||
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
||||
@ -634,7 +639,5 @@ class Repository(
|
||||
_selectedSource = null
|
||||
}
|
||||
|
||||
fun getSelectedSource(): SelfossModel.SourceDetail? {
|
||||
return _selectedSource
|
||||
}
|
||||
fun getSelectedSource(): SelfossModel.SourceDetail? = _selectedSource
|
||||
}
|
@ -17,8 +17,8 @@ import kotlinx.serialization.json.Json
|
||||
class MercuryApi {
|
||||
var client = createHttpClient()
|
||||
|
||||
private fun createHttpClient(): HttpClient {
|
||||
return HttpClient {
|
||||
private fun createHttpClient(): HttpClient =
|
||||
HttpClient {
|
||||
install(HttpCache)
|
||||
install(ContentNegotiation) {
|
||||
json(
|
||||
@ -40,7 +40,6 @@ class MercuryApi {
|
||||
}
|
||||
expectSuccess = false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> =
|
||||
bodyOrFailure(
|
||||
@ -48,4 +47,4 @@ class MercuryApi {
|
||||
parameter("link", url)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -3,23 +3,28 @@ package bou.amine.apps.readerforselfossv2.rest
|
||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
||||
import bou.amine.apps.readerforselfossv2.model.SuccessResponse
|
||||
import io.github.aakira.napier.Napier
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.forms.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.delete
|
||||
import io.ktor.client.request.forms.submitForm
|
||||
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 {
|
||||
return if (r != null && r.status === HttpStatusCode.NotFound) {
|
||||
suspend fun responseOrSuccessIf404(r: HttpResponse?): SuccessResponse =
|
||||
if (r != null && r.status === HttpStatusCode.NotFound) {
|
||||
SuccessResponse(true)
|
||||
} else {
|
||||
maybeResponse(r)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun maybeResponse(r: HttpResponse?): SuccessResponse {
|
||||
return if (r != null && r.status.isSuccess()) {
|
||||
suspend fun maybeResponse(r: HttpResponse?): SuccessResponse =
|
||||
if (r != null && r.status.isSuccess()) {
|
||||
r.body()
|
||||
} else {
|
||||
if (r != null) {
|
||||
@ -27,7 +32,6 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse {
|
||||
}
|
||||
SuccessResponse(false)
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
|
||||
try {
|
||||
@ -98,4 +102,4 @@ suspend fun HttpClient.tryToSubmitForm(
|
||||
url(url)
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,16 +33,18 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
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()
|
||||
|
||||
fun createHttpClient() =
|
||||
HttpClient(CIO) {
|
||||
if (appSettingsService.getSelfSigned()) {
|
||||
engine {
|
||||
setupInsecureHTTPEngine(this)
|
||||
setupInsecureHttpEngine(this)
|
||||
}
|
||||
}
|
||||
install(HttpCache)
|
||||
@ -105,12 +107,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
|
||||
private fun hasLoginInfo() =
|
||||
appSettingsService.getUserName().isNotEmpty() &&
|
||||
appSettingsService.getPassword()
|
||||
.isNotEmpty()
|
||||
appSettingsService
|
||||
.getPassword()
|
||||
.isNotEmpty()
|
||||
|
||||
suspend fun login(): SuccessResponse =
|
||||
if (appSettingsService.getUserName().isNotEmpty() &&
|
||||
appSettingsService.getPassword()
|
||||
appSettingsService
|
||||
.getPassword()
|
||||
.isNotEmpty()
|
||||
) {
|
||||
if (shouldHavePostLogin()) {
|
||||
@ -127,8 +131,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
client.tryToGet(url("/login")) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -150,8 +156,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
client.tryToPost(url("/login")) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -168,8 +176,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
},
|
||||
)
|
||||
|
||||
private fun shouldHaveNewLogout() =
|
||||
appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
|
||||
private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
|
||||
|
||||
suspend fun logout(): SuccessResponse =
|
||||
if (shouldHaveNewLogout()) {
|
||||
@ -181,8 +188,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
private suspend fun maybeLogoutIfAvailable() =
|
||||
responseOrSuccessIf404(
|
||||
client.tryToGet(url("/logout")) {
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -202,8 +211,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
private suspend fun doLogout() =
|
||||
maybeResponse(
|
||||
client.tryToDelete(url("/api/session/current")) {
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -242,8 +253,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("updatedsince", updatedSince)
|
||||
parameter("items", items ?: appSettingsService.getItemsNumber())
|
||||
parameter("offset", offset)
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -269,8 +282,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
}
|
||||
parameter("type", "all")
|
||||
parameter("items", 1)
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -294,8 +309,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -319,8 +336,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -344,8 +363,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -369,8 +390,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -394,8 +417,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -419,8 +444,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -440,8 +467,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> =
|
||||
bodyOrFailure(
|
||||
client.tryToGet(url("/api/about")) {
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -465,8 +494,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -490,8 +521,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -515,8 +548,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -540,8 +575,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -563,16 +600,18 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
client.tryToSubmitForm(
|
||||
url = url("/mark"),
|
||||
formParameters =
|
||||
Parameters.build {
|
||||
if (!shouldHavePostLogin()) {
|
||||
append("username", appSettingsService.getUserName())
|
||||
append("password", appSettingsService.getPassword())
|
||||
}
|
||||
ids.map { append("ids[]", it) }
|
||||
},
|
||||
Parameters.build {
|
||||
if (!shouldHavePostLogin()) {
|
||||
append("username", appSettingsService.getUserName())
|
||||
append("password", appSettingsService.getPassword())
|
||||
}
|
||||
ids.map { append("ids[]", it) }
|
||||
},
|
||||
block = {
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -614,19 +653,21 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
client.tryToSubmitForm(
|
||||
url = url("/source"),
|
||||
formParameters =
|
||||
Parameters.build {
|
||||
if (!shouldHavePostLogin()) {
|
||||
append("username", appSettingsService.getUserName())
|
||||
append("password", appSettingsService.getPassword())
|
||||
}
|
||||
append("title", title)
|
||||
append("url", url)
|
||||
append("spout", spout)
|
||||
append(tagsParamName, tags)
|
||||
},
|
||||
Parameters.build {
|
||||
if (!shouldHavePostLogin()) {
|
||||
append("username", appSettingsService.getUserName())
|
||||
append("password", appSettingsService.getPassword())
|
||||
}
|
||||
append("title", title)
|
||||
append("url", url)
|
||||
append("spout", spout)
|
||||
append(tagsParamName, tags)
|
||||
},
|
||||
block = {
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -669,19 +710,21 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
client.tryToSubmitForm(
|
||||
url = url("/source/$id"),
|
||||
formParameters =
|
||||
Parameters.build {
|
||||
if (!shouldHavePostLogin()) {
|
||||
append("username", appSettingsService.getUserName())
|
||||
append("password", appSettingsService.getPassword())
|
||||
}
|
||||
append("title", title)
|
||||
append("url", url)
|
||||
append("spout", spout)
|
||||
append(tagsParamName, tags)
|
||||
},
|
||||
Parameters.build {
|
||||
if (!shouldHavePostLogin()) {
|
||||
append("username", appSettingsService.getUserName())
|
||||
append("password", appSettingsService.getPassword())
|
||||
}
|
||||
append("title", title)
|
||||
append("url", url)
|
||||
append("spout", spout)
|
||||
append(tagsParamName, tags)
|
||||
},
|
||||
block = {
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -705,8 +748,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
parameter("username", appSettingsService.getUserName())
|
||||
parameter("password", appSettingsService.getPassword())
|
||||
}
|
||||
if (appSettingsService.getBasicUserName()
|
||||
.isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()
|
||||
if (appSettingsService
|
||||
.getBasicUserName()
|
||||
.isNotEmpty() &&
|
||||
appSettingsService.getBasicPassword().isNotEmpty()
|
||||
) {
|
||||
headers {
|
||||
append(
|
||||
@ -722,4 +767,4 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -121,4 +121,4 @@ class ACRASettings : Settings {
|
||||
longs.remove(key)
|
||||
strings.remove(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,9 @@ package bou.amine.apps.readerforselfossv2.service
|
||||
|
||||
import com.russhwolf.settings.Settings
|
||||
|
||||
class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
class AppSettingsService(
|
||||
acraSenderServiceProcess: Boolean = false,
|
||||
) {
|
||||
val settings: Settings =
|
||||
if (acraSenderServiceProcess) {
|
||||
ACRASettings()
|
||||
@ -11,37 +13,37 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
}
|
||||
|
||||
// Api related
|
||||
private var _apiVersion: Int = -1
|
||||
private var _publicAccess: Boolean? = null
|
||||
private var _selfSigned: Boolean? = null
|
||||
private var _baseUrl: String = ""
|
||||
private var _userName: String = ""
|
||||
private var _basicUserName: String = ""
|
||||
private var _password: String = ""
|
||||
private var _basicPassword: String = ""
|
||||
private var apiVersion: Int = -1
|
||||
private var publicAccess: Boolean? = null
|
||||
private var selfSigned: Boolean? = null
|
||||
private var baseUrl: String = ""
|
||||
private var userName: String = ""
|
||||
private var basicUserName: String = ""
|
||||
private var password: String = ""
|
||||
private var basicPassword: String = ""
|
||||
|
||||
// User settings related
|
||||
private var _itemsCaching: Boolean? = null
|
||||
private var _articleViewer: Boolean? = null
|
||||
private var _shouldBeCardView: Boolean? = null
|
||||
private var _displayUnreadCount: Boolean? = null
|
||||
private var _displayAllCount: Boolean? = null
|
||||
private var _fullHeightCards: Boolean? = null
|
||||
private var _updateSources: Boolean? = null
|
||||
private var _periodicRefresh: Boolean? = null
|
||||
private var _refreshWhenChargingOnly: Boolean? = null
|
||||
private var _infiniteLoading: Boolean? = null
|
||||
private var _notifyNewItems: Boolean? = null
|
||||
private var _itemsNumber: Int? = null
|
||||
private var _apiTimeout: Long? = null
|
||||
private var _refreshMinutes: Long = 360
|
||||
private var _markOnScroll: Boolean? = null
|
||||
private var _activeAlignment: Int? = null
|
||||
private var itemsCaching: Boolean? = null
|
||||
private var articleViewer: Boolean? = null
|
||||
private var shouldBeCardView: Boolean? = null
|
||||
private var displayUnreadCount: Boolean? = null
|
||||
private var displayAllCount: Boolean? = null
|
||||
private var fullHeightCards: Boolean? = null
|
||||
private var updateSources: Boolean? = null
|
||||
private var periodicRefresh: Boolean? = null
|
||||
private var refreshWhenChargingOnly: Boolean? = null
|
||||
private var infiniteLoading: Boolean? = null
|
||||
private var notifyNewItems: Boolean? = null
|
||||
private var itemsNumber: Int? = null
|
||||
private var apiTimeout: Long? = null
|
||||
private var refreshMinutes: Long = 360
|
||||
private var markOnScroll: Boolean? = null
|
||||
private var activeAlignment: Int? = null
|
||||
|
||||
private var _fontSize: Int? = null
|
||||
private var _staticBar: Boolean? = null
|
||||
private var _font: String = ""
|
||||
private var _theme: Int? = null
|
||||
private var fontSize: Int? = null
|
||||
private var staticBar: Boolean? = null
|
||||
private var font: String = ""
|
||||
private var theme: Int? = null
|
||||
|
||||
init {
|
||||
refreshApiSettings()
|
||||
@ -49,11 +51,11 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
}
|
||||
|
||||
fun getApiVersion(): Int {
|
||||
if (_apiVersion == -1) {
|
||||
if (apiVersion == -1) {
|
||||
refreshApiVersion()
|
||||
return _apiVersion
|
||||
return apiVersion
|
||||
}
|
||||
return _apiVersion
|
||||
return apiVersion
|
||||
}
|
||||
|
||||
fun updateApiVersion(apiMajorVersion: Int) {
|
||||
@ -62,14 +64,14 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
}
|
||||
|
||||
private fun refreshApiVersion() {
|
||||
_apiVersion = settings.getInt(API_VERSION_MAJOR, -1)
|
||||
apiVersion = settings.getInt(API_VERSION_MAJOR, -1)
|
||||
}
|
||||
|
||||
fun getPublicAccess(): Boolean {
|
||||
if (_publicAccess == null) {
|
||||
if (publicAccess == null) {
|
||||
refreshPublicAccess()
|
||||
}
|
||||
return _publicAccess!!
|
||||
return publicAccess!!
|
||||
}
|
||||
|
||||
fun updatePublicAccess(publicAccess: Boolean) {
|
||||
@ -78,14 +80,14 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
}
|
||||
|
||||
private fun refreshPublicAccess() {
|
||||
_publicAccess = settings.getBoolean(API_PUBLIC_ACCESS, false)
|
||||
publicAccess = settings.getBoolean(API_PUBLIC_ACCESS, false)
|
||||
}
|
||||
|
||||
fun getSelfSigned(): Boolean {
|
||||
if (_selfSigned == null) {
|
||||
if (selfSigned == null) {
|
||||
refreshSelfSigned()
|
||||
}
|
||||
return _selfSigned!!
|
||||
return selfSigned!!
|
||||
}
|
||||
|
||||
fun updateSelfSigned(selfSigned: Boolean) {
|
||||
@ -94,53 +96,53 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
}
|
||||
|
||||
private fun refreshSelfSigned() {
|
||||
_selfSigned = settings.getBoolean(API_SELF_SIGNED, false)
|
||||
selfSigned = settings.getBoolean(API_SELF_SIGNED, false)
|
||||
}
|
||||
|
||||
fun getBaseUrl(): String {
|
||||
if (_baseUrl.isEmpty()) {
|
||||
if (baseUrl.isEmpty()) {
|
||||
refreshBaseUrl()
|
||||
}
|
||||
return _baseUrl
|
||||
return baseUrl
|
||||
}
|
||||
|
||||
fun getUserName(): String {
|
||||
if (_userName.isEmpty()) {
|
||||
if (userName.isEmpty()) {
|
||||
refreshUsername()
|
||||
}
|
||||
return _userName
|
||||
return userName
|
||||
}
|
||||
|
||||
fun getPassword(): String {
|
||||
if (_password.isEmpty()) {
|
||||
if (password.isEmpty()) {
|
||||
refreshPassword()
|
||||
}
|
||||
return _password
|
||||
return password
|
||||
}
|
||||
|
||||
fun getBasicUserName(): String {
|
||||
if (_basicUserName.isEmpty()) {
|
||||
if (basicUserName.isEmpty()) {
|
||||
refreshBasicUsername()
|
||||
}
|
||||
return _basicUserName
|
||||
return basicUserName
|
||||
}
|
||||
|
||||
fun getBasicPassword(): String {
|
||||
if (_basicPassword.isEmpty()) {
|
||||
if (basicPassword.isEmpty()) {
|
||||
refreshBasicPassword()
|
||||
}
|
||||
return _basicPassword
|
||||
return basicPassword
|
||||
}
|
||||
|
||||
fun getItemsNumber(): Int {
|
||||
if (_itemsNumber == null) {
|
||||
if (itemsNumber == null) {
|
||||
refreshItemsNumber()
|
||||
}
|
||||
return _itemsNumber!!
|
||||
return itemsNumber!!
|
||||
}
|
||||
|
||||
private fun refreshItemsNumber() {
|
||||
_itemsNumber =
|
||||
itemsNumber =
|
||||
try {
|
||||
settings.getString(API_ITEMS_NUMBER, "20").toInt()
|
||||
} catch (e: Exception) {
|
||||
@ -150,16 +152,16 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
}
|
||||
|
||||
fun getApiTimeout(): Long {
|
||||
if (_apiTimeout == null) {
|
||||
if (apiTimeout == null) {
|
||||
refreshApiTimeout()
|
||||
}
|
||||
return _apiTimeout!!
|
||||
return apiTimeout!!
|
||||
}
|
||||
|
||||
private fun secToMs(n: Long) = n * 1000
|
||||
|
||||
private fun refreshApiTimeout() {
|
||||
_apiTimeout =
|
||||
apiTimeout =
|
||||
secToMs(
|
||||
try {
|
||||
val settingsTimeout = settings.getString(API_TIMEOUT, "60")
|
||||
@ -177,229 +179,229 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
}
|
||||
|
||||
private fun refreshBaseUrl() {
|
||||
_baseUrl = settings.getString(BASE_URL, "")
|
||||
baseUrl = settings.getString(BASE_URL, "")
|
||||
}
|
||||
|
||||
private fun refreshUsername() {
|
||||
_userName = settings.getString(LOGIN, "")
|
||||
userName = settings.getString(LOGIN, "")
|
||||
}
|
||||
|
||||
private fun refreshPassword() {
|
||||
_password = settings.getString(PASSWORD, "")
|
||||
password = settings.getString(PASSWORD, "")
|
||||
}
|
||||
|
||||
private fun refreshBasicUsername() {
|
||||
_basicUserName = settings.getString(BASIC_LOGIN, "")
|
||||
basicUserName = settings.getString(BASIC_LOGIN, "")
|
||||
}
|
||||
|
||||
private fun refreshBasicPassword() {
|
||||
_basicPassword = settings.getString(BASIC_PASSWORD, "")
|
||||
basicPassword = settings.getString(BASIC_PASSWORD, "")
|
||||
}
|
||||
|
||||
private fun refreshArticleViewerEnabled() {
|
||||
_articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true)
|
||||
articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true)
|
||||
}
|
||||
|
||||
fun isArticleViewerEnabled(): Boolean {
|
||||
if (_articleViewer != null) {
|
||||
if (articleViewer != null) {
|
||||
refreshArticleViewerEnabled()
|
||||
}
|
||||
return _articleViewer == true
|
||||
return articleViewer == true
|
||||
}
|
||||
|
||||
private fun refreshShouldBeCardViewEnabled() {
|
||||
_shouldBeCardView = settings.getBoolean(CARD_VIEW_ACTIVE, false)
|
||||
shouldBeCardView = settings.getBoolean(CARD_VIEW_ACTIVE, false)
|
||||
}
|
||||
|
||||
fun isCardViewEnabled(): Boolean {
|
||||
if (_shouldBeCardView != null) {
|
||||
if (shouldBeCardView != null) {
|
||||
refreshShouldBeCardViewEnabled()
|
||||
}
|
||||
return _shouldBeCardView == true
|
||||
return shouldBeCardView == true
|
||||
}
|
||||
|
||||
private fun refreshDisplayUnreadCountEnabled() {
|
||||
_displayUnreadCount = settings.getBoolean(DISPLAY_UNREAD_COUNT, true)
|
||||
displayUnreadCount = settings.getBoolean(DISPLAY_UNREAD_COUNT, true)
|
||||
}
|
||||
|
||||
fun isDisplayUnreadCountEnabled(): Boolean {
|
||||
if (_displayUnreadCount != null) {
|
||||
if (displayUnreadCount != null) {
|
||||
refreshDisplayUnreadCountEnabled()
|
||||
}
|
||||
return _displayUnreadCount == true
|
||||
return displayUnreadCount == true
|
||||
}
|
||||
|
||||
private fun refreshDisplayAllCountEnabled() {
|
||||
_displayAllCount = settings.getBoolean(DISPLAY_OTHER_COUNT, false)
|
||||
displayAllCount = settings.getBoolean(DISPLAY_OTHER_COUNT, false)
|
||||
}
|
||||
|
||||
fun isDisplayAllCountEnabled(): Boolean {
|
||||
if (_displayAllCount != null) {
|
||||
if (displayAllCount != null) {
|
||||
refreshDisplayAllCountEnabled()
|
||||
}
|
||||
return _displayAllCount == true
|
||||
return displayAllCount == true
|
||||
}
|
||||
|
||||
private fun refreshFullHeightCardsEnabled() {
|
||||
_fullHeightCards = settings.getBoolean(FULL_HEIGHT_CARDS, false)
|
||||
fullHeightCards = settings.getBoolean(FULL_HEIGHT_CARDS, false)
|
||||
}
|
||||
|
||||
fun isFullHeightCardsEnabled(): Boolean {
|
||||
if (_fullHeightCards != null) {
|
||||
if (fullHeightCards != null) {
|
||||
refreshFullHeightCardsEnabled()
|
||||
}
|
||||
return _fullHeightCards == true
|
||||
return fullHeightCards == true
|
||||
}
|
||||
|
||||
private fun refreshUpdateSourcesEnabled() {
|
||||
_updateSources = settings.getBoolean(UPDATE_SOURCES, true)
|
||||
updateSources = settings.getBoolean(UPDATE_SOURCES, true)
|
||||
}
|
||||
|
||||
fun isUpdateSourcesEnabled(): Boolean {
|
||||
if (_updateSources != null) {
|
||||
if (updateSources != null) {
|
||||
refreshUpdateSourcesEnabled()
|
||||
}
|
||||
return _updateSources == true
|
||||
return updateSources == true
|
||||
}
|
||||
|
||||
private fun refreshPeriodicRefreshEnabled() {
|
||||
_periodicRefresh = settings.getBoolean(PERIODIC_REFRESH, false)
|
||||
periodicRefresh = settings.getBoolean(PERIODIC_REFRESH, false)
|
||||
}
|
||||
|
||||
fun isPeriodicRefreshEnabled(): Boolean {
|
||||
if (_periodicRefresh != null) {
|
||||
if (periodicRefresh != null) {
|
||||
refreshPeriodicRefreshEnabled()
|
||||
}
|
||||
return _periodicRefresh == true
|
||||
return periodicRefresh == true
|
||||
}
|
||||
|
||||
private fun refreshRefreshWhenChargingOnlyEnabled() {
|
||||
_refreshWhenChargingOnly = settings.getBoolean(REFRESH_WHEN_CHARGING, false)
|
||||
refreshWhenChargingOnly = settings.getBoolean(REFRESH_WHEN_CHARGING, false)
|
||||
}
|
||||
|
||||
fun isRefreshWhenChargingOnlyEnabled(): Boolean {
|
||||
if (_refreshWhenChargingOnly != null) {
|
||||
if (refreshWhenChargingOnly != null) {
|
||||
refreshRefreshWhenChargingOnlyEnabled()
|
||||
}
|
||||
return _refreshWhenChargingOnly == true
|
||||
return refreshWhenChargingOnly == true
|
||||
}
|
||||
|
||||
private fun refreshRefreshMinutes() {
|
||||
_refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, "360").toLong()
|
||||
if (_refreshMinutes <= 15) {
|
||||
_refreshMinutes = 15
|
||||
refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, "360").toLong()
|
||||
if (refreshMinutes <= 15) {
|
||||
refreshMinutes = 15
|
||||
}
|
||||
}
|
||||
|
||||
fun getRefreshMinutes(): Long {
|
||||
if (_refreshMinutes != 360L) {
|
||||
if (refreshMinutes != 360L) {
|
||||
refreshRefreshMinutes()
|
||||
}
|
||||
return _refreshMinutes
|
||||
return refreshMinutes
|
||||
}
|
||||
|
||||
private fun refreshInfiniteLoadingEnabled() {
|
||||
_infiniteLoading = settings.getBoolean(INFINITE_LOADING, false)
|
||||
infiniteLoading = settings.getBoolean(INFINITE_LOADING, false)
|
||||
}
|
||||
|
||||
fun isInfiniteLoadingEnabled(): Boolean {
|
||||
if (_infiniteLoading != null) {
|
||||
if (infiniteLoading != null) {
|
||||
refreshInfiniteLoadingEnabled()
|
||||
}
|
||||
return _infiniteLoading == true
|
||||
return infiniteLoading == true
|
||||
}
|
||||
|
||||
private fun refreshItemCachingEnabled() {
|
||||
_itemsCaching = settings.getBoolean(ITEMS_CACHING, false)
|
||||
itemsCaching = settings.getBoolean(ITEMS_CACHING, false)
|
||||
}
|
||||
|
||||
fun isItemCachingEnabled(): Boolean {
|
||||
if (_itemsCaching != null) {
|
||||
if (itemsCaching != null) {
|
||||
refreshItemCachingEnabled()
|
||||
}
|
||||
return _itemsCaching == true
|
||||
return itemsCaching == true
|
||||
}
|
||||
|
||||
private fun refreshNotifyNewItemsEnabled() {
|
||||
_notifyNewItems = settings.getBoolean(NOTIFY_NEW_ITEMS, false)
|
||||
notifyNewItems = settings.getBoolean(NOTIFY_NEW_ITEMS, false)
|
||||
}
|
||||
|
||||
fun isNotifyNewItemsEnabled(): Boolean {
|
||||
if (_notifyNewItems != null) {
|
||||
if (notifyNewItems != null) {
|
||||
refreshNotifyNewItemsEnabled()
|
||||
}
|
||||
return _notifyNewItems == true
|
||||
return notifyNewItems == true
|
||||
}
|
||||
|
||||
private fun refreshMarkOnScrollEnabled() {
|
||||
_markOnScroll = settings.getBoolean(MARK_ON_SCROLL, false)
|
||||
markOnScroll = settings.getBoolean(MARK_ON_SCROLL, false)
|
||||
}
|
||||
|
||||
fun isMarkOnScrollEnabled(): Boolean {
|
||||
if (_markOnScroll != null) {
|
||||
if (markOnScroll != null) {
|
||||
refreshMarkOnScrollEnabled()
|
||||
}
|
||||
return _markOnScroll == true
|
||||
return markOnScroll == true
|
||||
}
|
||||
|
||||
private fun refreshActiveAllignment() {
|
||||
_activeAlignment = settings.getInt(TEXT_ALIGN, JUSTIFY)
|
||||
activeAlignment = settings.getInt(TEXT_ALIGN, JUSTIFY)
|
||||
}
|
||||
|
||||
fun getActiveAllignment(): Int {
|
||||
if (_activeAlignment != null) {
|
||||
if (activeAlignment != null) {
|
||||
refreshActiveAllignment()
|
||||
}
|
||||
return _activeAlignment ?: JUSTIFY
|
||||
return activeAlignment ?: JUSTIFY
|
||||
}
|
||||
|
||||
fun changeAllignment(allignment: Int) {
|
||||
settings.putInt(TEXT_ALIGN, allignment)
|
||||
_activeAlignment = allignment
|
||||
activeAlignment = allignment
|
||||
}
|
||||
|
||||
private fun refreshFontSize() {
|
||||
_fontSize = settings.getString(READER_FONT_SIZE, "16").toInt()
|
||||
fontSize = settings.getString(READER_FONT_SIZE, "16").toInt()
|
||||
}
|
||||
|
||||
fun getFontSize(): Int {
|
||||
if (_fontSize != null) {
|
||||
if (fontSize != null) {
|
||||
refreshFontSize()
|
||||
}
|
||||
return _fontSize ?: 16
|
||||
return fontSize ?: 16
|
||||
}
|
||||
|
||||
private fun refreshStaticBarEnabled() {
|
||||
_staticBar = settings.getBoolean(READER_STATIC_BAR, false)
|
||||
staticBar = settings.getBoolean(READER_STATIC_BAR, false)
|
||||
}
|
||||
|
||||
fun isStaticBarEnabled(): Boolean {
|
||||
if (_staticBar != null) {
|
||||
if (staticBar != null) {
|
||||
refreshStaticBarEnabled()
|
||||
}
|
||||
return _staticBar == true
|
||||
return staticBar == true
|
||||
}
|
||||
|
||||
private fun refreshFont() {
|
||||
_font = settings.getString(READER_FONT, "")
|
||||
font = settings.getString(READER_FONT, "")
|
||||
}
|
||||
|
||||
fun getFont(): String {
|
||||
if (_font.isEmpty()) {
|
||||
if (font.isEmpty()) {
|
||||
refreshFont()
|
||||
}
|
||||
return _font
|
||||
return font
|
||||
}
|
||||
|
||||
private fun refreshCurrentTheme() {
|
||||
_theme = settings.getString(CURRENT_THEME, "-1").toInt()
|
||||
theme = settings.getString(CURRENT_THEME, "-1").toInt()
|
||||
}
|
||||
|
||||
fun getCurrentTheme(): Int {
|
||||
if (_theme == null) {
|
||||
if (theme == null) {
|
||||
refreshCurrentTheme()
|
||||
}
|
||||
return _theme ?: -1
|
||||
return theme ?: -1
|
||||
}
|
||||
|
||||
fun refreshApiSettings() {
|
||||
@ -478,15 +480,15 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
@ -17,7 +17,11 @@ fun String.toParsedDate(): Long {
|
||||
if (this.matches(oldVersionFormat)) {
|
||||
this.replace(" ", "T")
|
||||
} 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 {
|
||||
throw Exception("Unrecognized format for $this")
|
||||
}
|
||||
|
@ -82,4 +82,4 @@ fun SelfossModel.Tag.getColorHexCode(): String =
|
||||
"#$char1$char1$char2$char2$char3$char3"
|
||||
} else {
|
||||
this.color
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
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"),
|
||||
ALL(2, "all"),
|
||||
STARRED(3, "starred"),
|
@ -8,27 +8,29 @@ import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class DatesTest {
|
||||
private val newVersionDateVariant = "2022-12-24T17:00:08+00"
|
||||
private val newVersionDate = "2013-04-07T13:43:00+01:00"
|
||||
private val newVersionDate2 = "2013-04-07T13:43:00-01:00"
|
||||
private val oldVersionDate = "2013-05-07 13:46:00"
|
||||
private val oldVersionDateVariant = "2021-03-21 10:32:00.000000"
|
||||
|
||||
private val newVersionDateVariant = "2022-12-24T17:00:08+00"
|
||||
private val newVersionDate = "2013-04-07T13:43:00+01:00"
|
||||
private val newVersionDate2 = "2013-04-07T13:43:00-01:00"
|
||||
private val oldVersionDate = "2013-05-07 13:46:00"
|
||||
private val oldVersionDateVariant = "2021-03-21 10:32:00.000000"
|
||||
|
||||
@Test
|
||||
fun new_version_date_should_be_parsed() {
|
||||
val date = newVersionDate.toParsedDate()
|
||||
val expected =
|
||||
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||
LocalDateTime(2013, 4, 7, 13, 43, 0, 0)
|
||||
.toInstant(TimeZone.currentSystemDefault())
|
||||
.toEpochMilliseconds()
|
||||
|
||||
assertEquals(expected, date)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun new_version_date2_should_be_parsed() {
|
||||
val date = newVersionDate2.toParsedDate()
|
||||
val expected =
|
||||
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||
LocalDateTime(2013, 4, 7, 13, 43, 0, 0)
|
||||
.toInstant(TimeZone.currentSystemDefault())
|
||||
.toEpochMilliseconds()
|
||||
|
||||
assertEquals(expected, date)
|
||||
@ -38,7 +40,8 @@ class DatesTest {
|
||||
fun old_version_date_should_be_parsed() {
|
||||
val date = oldVersionDate.toParsedDate()
|
||||
val expected =
|
||||
LocalDateTime(2013, 5, 7, 13, 46, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||
LocalDateTime(2013, 5, 7, 13, 46, 0, 0)
|
||||
.toInstant(TimeZone.currentSystemDefault())
|
||||
.toEpochMilliseconds()
|
||||
|
||||
assertEquals(expected, date)
|
||||
@ -48,7 +51,8 @@ class DatesTest {
|
||||
fun old_version_variant_date_should_be_parsed() {
|
||||
val date = oldVersionDateVariant.toParsedDate()
|
||||
val expected =
|
||||
LocalDateTime(2021, 3, 21, 10, 32, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||
LocalDateTime(2021, 3, 21, 10, 32, 0, 0)
|
||||
.toInstant(TimeZone.currentSystemDefault())
|
||||
.toEpochMilliseconds()
|
||||
|
||||
assertEquals(expected, date)
|
||||
@ -58,7 +62,8 @@ class DatesTest {
|
||||
fun new_version_variant_date_should_be_parsed() {
|
||||
val date = newVersionDateVariant.toParsedDate()
|
||||
val expected =
|
||||
LocalDateTime(2022, 12, 24, 17, 0, 8, 0).toInstant(TimeZone.currentSystemDefault())
|
||||
LocalDateTime(2022, 12, 24, 17, 0, 8, 0)
|
||||
.toInstant(TimeZone.currentSystemDefault())
|
||||
.toEpochMilliseconds()
|
||||
|
||||
assertEquals(expected, date)
|
||||
|
@ -4,7 +4,5 @@ import app.cash.sqldelight.db.SqlDriver
|
||||
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
||||
|
||||
actual class DriverFactory {
|
||||
actual fun createDriver(): SqlDriver {
|
||||
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||
}
|
||||
}
|
||||
actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||
}
|
||||
|
@ -2,5 +2,5 @@ package bou.amine.apps.readerforselfossv2.rest
|
||||
|
||||
import io.ktor.client.engine.cio.CIOEngineConfig
|
||||
|
||||
actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) {
|
||||
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
||||
}
|
@ -6,4 +6,4 @@ actual class DateUtils {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,4 +6,4 @@ actual class DateUtils actual constructor() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,5 @@ import app.cash.sqldelight.db.SqlDriver
|
||||
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
||||
|
||||
actual class DriverFactory {
|
||||
actual fun createDriver(): SqlDriver {
|
||||
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||
}
|
||||
}
|
||||
actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||
}
|
||||
|
@ -2,5 +2,5 @@ package bou.amine.apps.readerforselfossv2.rest
|
||||
|
||||
import io.ktor.client.engine.cio.CIOEngineConfig
|
||||
|
||||
actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) {
|
||||
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
||||
}
|
@ -6,4 +6,4 @@ actual class DateUtils {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user