Debug trying to fix context issues. #174
							
								
								
									
										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") | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user