From c79ab5e92b5ad89f6e649f65253f5a0c52acd354 Mon Sep 17 00:00:00 2001 From: Amine Date: Sat, 11 Jan 2025 20:35:27 +0000 Subject: [PATCH] Debug trying to fix context issues. (#174) Reviewed-on: https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/174 Co-authored-by: Amine Co-committed-by: Amine --- .editorconfig | 36 + .gitea/workflows/on_pr.yml | 10 +- .../readerforselfossv2/android/CommonTests.kt | 52 +- .../android/{helpers.kt => Helpers.kt} | 70 +- .../android/HomeActivityTest.kt | 26 +- .../android/LoginActivityTest.kt | 32 +- .../android/SettingsActivityGeneralTest.kt | 85 +- .../android/SettingsActivityOfflineTest.kt | 95 ++- .../android/SettingsActivityReaderTest.kt | 25 +- .../android/SettingsActivityTest.kt | 16 +- .../android/SourcesActivityTest.kt | 9 +- .../android/HomeActivity.kt | 133 +-- .../android/ImageActivity.kt | 4 +- .../android/LoginActivity.kt | 44 +- .../apps/readerforselfossv2/android/MyApp.kt | 40 +- .../android/ReaderActivity.kt | 24 +- .../android/SourcesActivity.kt | 17 +- .../android/UpsertSourceActivity.kt | 28 +- .../android/adapters/ItemCardAdapter.kt | 24 +- .../android/adapters/ItemListAdapter.kt | 19 +- .../android/adapters/ItemsAdapter.kt | 15 +- .../android/adapters/SourcesListAdapter.kt | 18 +- .../{background.kt => LoadingWorker.kt} | 34 +- .../android/fragments/ArticleFragment.kt | 234 +++--- .../android/fragments/FilterSheetFragment.kt | 15 +- .../android/fragments/ImageFragment.kt | 5 +- .../android/model/AndroidIModelUtils.kt | 13 +- .../android/settings/SettingsActivity.kt | 76 +- .../CountingIdlingResourceSingleton.kt | 3 +- .../android/testing/TestingHelper.kt | 3 +- .../android/utils/AppUtils.kt | 9 +- .../android/utils/CircleImageView.kt | 4 +- .../android/utils/LinksUtils.kt | 20 +- .../android/utils/acra/ACRAUtils.kt | 2 +- .../utils/acra/AcraReportingAdministrator.kt | 17 +- .../android/utils/glide/GlideUtils.kt | 10 +- .../android/utils/network/NetworkUtils.kt | 21 +- .../android/viewmodel/AppViewModel.kt | 13 +- androidApp/src/main/res/xml/pref_theme.xml | 13 - .../robolectric/{helpers.kt => Helpers.kt} | 10 +- .../tests/robolectric/LoginActivityTest.kt | 6 +- .../robolectric/RobotElectriqueRunner.kt | 12 +- .../tests/repository/RepositoryTest.kt | 157 ++-- .../tests/repository/TestUtils.kt | 12 +- detekt.yml | 786 ++++++++++++++++++ .../{DeviceDatabase.kt => DriverFactory.kt} | 13 +- .../readerforselfossv2/rest/NetworkSSL.kt | 10 +- .../readerforselfossv2/utils/DateUtils.kt | 4 +- .../utils/ModelConverters.kt | 31 +- .../{DeviceDatabase.kt => DriverFactory.kt} | 2 +- .../{DI/modules.kt => di/Modules.kt} | 2 +- .../readerforselfossv2/model/MercuryModel.kt | 5 +- .../readerforselfossv2/model/ResultModel.kt | 17 +- .../readerforselfossv2/model/SelfossModel.kt | 28 +- .../{RepositoryImpl.kt => Repository.kt} | 83 +- .../readerforselfossv2/rest/MercuryApi.kt | 7 +- .../apps/readerforselfossv2/rest/RestUtils.kt | 31 +- .../readerforselfossv2/rest/SelfossApi.kt | 205 +++-- .../service/ACRASettings.kt | 4 +- .../service/AppSettingsService.kt | 271 +++--- .../readerforselfossv2/utils/DateUtils.kt | 17 +- .../readerforselfossv2/utils/EntityUtils.kt | 5 +- .../utils/{Enums.kt => ItemType.kt} | 6 +- .../readerforselfossv2/utils/StringUtils.kt | 1 + .../repository}/DatesTest.kt | 27 +- .../readerforselfossv2/dao/DriverFactory.kt | 6 +- .../rest/SetupInsecureHttpEngine.kt | 7 + .../rest/setupInsecureHTTPEngine.kt | 6 - .../readerforselfossv2/utils/DateUtils.kt | 3 +- ...tils.iosSimulatorArm64.kt => DateUtils.kt} | 3 +- .../readerforselfossv2/dao/DriverFactory.kt | 6 +- .../rest/SetupInsecureHttpEngine.kt | 7 + .../rest/setupInsecureHTTPEngine.kt | 6 - .../readerforselfossv2/utils/DateUtils.kt | 3 +- 74 files changed, 2077 insertions(+), 1036 deletions(-) create mode 100644 .editorconfig rename androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/{helpers.kt => Helpers.kt} (70%) rename androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/{background.kt => LoadingWorker.kt} (81%) delete mode 100644 androidApp/src/main/res/xml/pref_theme.xml rename androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/{helpers.kt => Helpers.kt} (85%) create mode 100644 detekt.yml rename shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/dao/{DeviceDatabase.kt => DriverFactory.kt} (57%) rename shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/dao/{DeviceDatabase.kt => DriverFactory.kt} (98%) rename shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/{DI/modules.kt => di/Modules.kt} (89%) rename shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/{RepositoryImpl.kt => Repository.kt} (91%) rename shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/{Enums.kt => ItemType.kt} (69%) rename shared/src/commonTest/kotlin/{ => bou/amine/apps/readerforselfossv2/repository}/DatesTest.kt (61%) create mode 100644 shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt delete mode 100644 shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/setupInsecureHTTPEngine.kt rename shared/src/iosSimulatorArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/{DateUtils.iosSimulatorArm64.kt => DateUtils.kt} (81%) create mode 100644 shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt delete mode 100644 shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/setupInsecureHTTPEngine.kt diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9ba7a88 --- /dev/null +++ b/.editorconfig @@ -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 \ No newline at end of file diff --git a/.gitea/workflows/on_pr.yml b/.gitea/workflows/on_pr.yml index f29a25c..183053d 100644 --- a/.gitea/workflows/on_pr.yml +++ b/.gitea/workflows/on_pr.yml @@ -16,13 +16,13 @@ jobs: java-version: '17' cache: gradle - name: Install klint - run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.0.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/ + run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.5.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/ - name: Install detekt - run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.1/detekt-cli-1.23.1.zip && unzip detekt-cli-1.23.1.zip + run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.7/detekt-cli-1.23.7.zip && unzip detekt-cli-1.23.7.zip - name: Linting... - 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 + run: ./detekt-cli-1.23.7/bin/detekt-cli -c detekt.yml --excludes '**/shared/build/**/*.kt' build: needs: Lint - uses: ./.gitea/workflows/common_build.yml \ No newline at end of file + uses: ./.gitea/workflows/common_build.yml diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/CommonTests.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/CommonTests.kt index b0e4cad..3e7187c 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/CommonTests.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/CommonTests.kt @@ -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())) -} \ No newline at end of file +} diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/helpers.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/Helpers.kt similarity index 70% rename from androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/helpers.kt rename to androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/Helpers.kt index ecca84b..207afcf 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/helpers.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/Helpers.kt @@ -25,38 +25,35 @@ import org.hamcrest.Matcher import org.hamcrest.Matchers import org.hamcrest.TypeSafeMatcher - -fun withError(@StringRes id: Int): TypeSafeMatcher { +fun withError( + @StringRes id: Int, +): TypeSafeMatcher { return object : TypeSafeMatcher() { override fun matchesSafely(view: View?): Boolean { - if (view == null) { - return false - } - val context = view.context - if (view !is EditText) { - return false - } - if (view.error == null) { + if (view != null && (view !is EditText || view.error == null)) { return false } + val context = view!!.context - return view.error.toString() == context.getString(id) + return (view as EditText).error.toString() == context.getString(id) } override fun describeTo(description: Description?) { + // Nothing } } } -fun isPopupWindow(): Matcher { - return isPlatformPopup() -} +fun isPopupWindow(): Matcher = isPlatformPopup() -fun withDrawable(@DrawableRes id: Int) = object : TypeSafeMatcher() { +fun withDrawable( + @DrawableRes id: Int, +) = object : TypeSafeMatcher() { override fun describeTo(description: Description) { description.appendText("ImageView with drawable same as drawable with id $id") } + @Suppress("detekt:SwallowedException") override fun matchesSafely(view: View): Boolean { val context = view.context val expectedBitmap = context.getDrawable(id)!!.toBitmap() @@ -68,43 +65,46 @@ fun withDrawable(@DrawableRes id: Int) = object : TypeSafeMatcher() { } } -fun hasBottombarItemText(@StringRes id: Int): Matcher? { - return allOf( +fun hasBottombarItemText( + @StringRes id: Int, +): Matcher? = + 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? { - return allOf( +fun withSettingsCheckboxWidget( + @StringRes id: Int, +): Matcher? = + allOf( withId(android.R.id.switch_widget), withParent( - withSettingsCheckboxFrame(id) - ) + withSettingsCheckboxFrame(id), + ), ) -} -fun withSettingsCheckboxFrame(@StringRes id: Int): Matcher? { - return allOf( +fun withSettingsCheckboxFrame( + @StringRes id: Int, +): Matcher? = + 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() + ApplicationProvider.getApplicationContext(), ) -} \ No newline at end of file +} diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/HomeActivityTest.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/HomeActivityTest.kt index eb76219..d52c424 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/HomeActivityTest.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/HomeActivityTest.kt @@ -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()))) } - -} \ No newline at end of file +} diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/LoginActivityTest.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/LoginActivityTest.kt index 43d8343..817a7d0 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/LoginActivityTest.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/LoginActivityTest.kt @@ -1,6 +1,5 @@ package bou.amine.apps.readerforselfossv2.android -import android.app.Activity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.IdlingRegistry import androidx.test.espresso.action.ViewActions.click @@ -23,40 +22,37 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @LargeTest class LoginActivityTest { - @get:Rule val activityRule = ActivityScenarioRule(LoginActivity::class.java) - private fun getActivity(): Activity? { - var activity: Activity? = null - activityRule.scenario.onActivity { - activity = it - } - return activity - } - @Before fun registerIdlingResource() { - IdlingRegistry.getInstance() + IdlingRegistry + .getInstance() .register(CountingIdlingResourceSingleton.countingIdlingResource) } @After fun unregisterIdlingResource() { - IdlingRegistry.getInstance() + IdlingRegistry + .getInstance() .unregister(CountingIdlingResourceSingleton.countingIdlingResource) } @Test fun viewIsInitialized() { onView(withId(R.id.urlView)).check(matches(isDisplayed())) - onView(withId(R.id.selfSigned)).check(matches(isDisplayed())).check(matches(isNotChecked())) + onView(withId(R.id.selfSigned)) + .check(matches(isDisplayed())) + .check(matches(isNotChecked())) .check( - matches(isClickable()) + 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 +76,4 @@ class LoginActivityTest { performLogin() onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed())) } -} \ No newline at end of file +} diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityGeneralTest.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityGeneralTest.kt index 4859b02..80463c2 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityGeneralTest.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityGeneralTest.kt @@ -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,81 +36,90 @@ 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()) } + @Suppress("detekt:LongMethod") @Test 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()), + ), + ), ) } + @Suppress("detekt:ForbiddenComment") @Test fun testGeneralActionsNumberItems() { onView(withText(R.string.pref_api_items_number_title)).perform(click()) @@ -120,25 +127,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 +164,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())) } -} \ No newline at end of file +} diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityOfflineTest.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityOfflineTest.kt index c2f192c..729901c 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityOfflineTest.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityOfflineTest.kt @@ -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,72 +36,79 @@ 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()) } + @Suppress("detekt:LongMethod") @Test fun testOffline() { 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(), + ), + ), ) } + @Suppress("detekt:LongMethod") @Test fun testOfflineActions() { onView(withText(R.string.pref_switch_items_caching_off)).check(matches(isDisplayed())) @@ -111,50 +116,50 @@ class SettingsActivityOfflineTest { onView(withText(R.string.pref_switch_items_caching_on)).check(matches(isDisplayed())) onView(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 +171,4 @@ class SettingsActivityOfflineTest { onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).perform(click()) onView(withSettingsCheckboxWidget(R.string.pref_switch_update_sources)).perform(click()) } -} \ No newline at end of file +} diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityReaderTest.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityReaderTest.kt index 88e1f27..cf4ffb6 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityReaderTest.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityReaderTest.kt @@ -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()) } } -} \ No newline at end of file +} diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityTest.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityTest.kt index 69101c6..3dcc96b 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityTest.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SettingsActivityTest.kt @@ -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())) } -} \ No newline at end of file +} diff --git a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SourcesActivityTest.kt b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SourcesActivityTest.kt index f7e0a57..403b7b1 100644 --- a/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SourcesActivityTest.kt +++ b/androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/SourcesActivityTest.kt @@ -41,11 +41,11 @@ class SourcesActivityTest { fun addSource() { testAddSourceWithUrl( "https://lorem-rss.herokuapp.com/feed?unit=year&interval=1&length=10", - sourceName + sourceName, ) } - + @Suppress("detekt:SwallowedException") @Test fun addSourceCheckContent() { testAddSourceWithUrl("https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en", sourceName) @@ -54,7 +54,7 @@ class SourcesActivityTest { onView(withText(R.string.menu_home_refresh)).perform(click()) onView(withText(R.string.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 +74,9 @@ class SourcesActivityTest { onView(withText(sourceName)).check(doesNotExist()) } - private fun goToSources() { openMenu() onView(withText(R.string.menu_home_sources)) .perform(click()) } -} \ No newline at end of file +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt index 84a85e8..5217411 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt @@ -49,7 +49,12 @@ import org.kodein.di.instance import java.security.MessageDigest import java.util.concurrent.TimeUnit -class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware { +private const val MIN_WIDTH_CARD_DP = 300 + +class HomeActivity : + AppCompatActivity(), + SearchView.OnQueryTextListener, + DIAware { private var items: ArrayList = ArrayList() private var elementsShown: ItemType = ItemType.UNREAD @@ -171,11 +176,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() } } } @@ -196,19 +202,23 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar } } + @Suppress("detekt:LongMethod") private fun handleBottomBar() { 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 +246,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, @@ -277,7 +285,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar handleBottomBarActions() - handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false)) + handleGdprDialog(appSettingsService.settings.getBoolean("GDPR_shown", false)) handleRecurringTask() CountingIdlingResourceSingleton.increment() @@ -289,10 +297,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar getElementsAccordingToTab() } - private fun handleGDPRDialog(GDPRShown: Boolean) { + private fun handleGdprDialog(gdprShown: Boolean) { val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256") messageDigest.update(appSettingsService.getBaseUrl().toByteArray()) - if (!GDPRShown) { + if (!gdprShown) { val alertDialog = AlertDialog.Builder(this).create() alertDialog.setTitle(getString(R.string.gdpr_dialog_title)) alertDialog.setMessage(getString(R.string.gdpr_dialog_message)) @@ -425,17 +433,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()) { @@ -538,7 +546,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar private fun calculateNoOfColumns(): Int { val displayMetrics = resources.displayMetrics val dpWidth = displayMetrics.widthPixels / displayMetrics.density - return (dpWidth / 300).toInt() + return (dpWidth / MIN_WIDTH_CARD_DP).toInt() } override fun onQueryTextChange(p0: String?): Boolean { @@ -577,7 +585,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() } @@ -586,10 +595,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar .show() } + @Suppress("detekt:ReturnCount", "detekt:LongMethod") 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 +616,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 +644,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 +673,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 +714,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 +724,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar val backgroundWork = PeriodicWorkRequestBuilder( 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, + ) } } -} \ No newline at end of file +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ImageActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ImageActivity.kt index 6221b6e..516c8bf 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ImageActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ImageActivity.kt @@ -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]) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt index 81b0b5a..ac3a41f 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt @@ -30,7 +30,11 @@ import org.kodein.di.DIAware import org.kodein.di.android.closestDI import org.kodein.di.instance -class LoginActivity : AppCompatActivity(), DIAware { +private const val MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED = 3 + +class LoginActivity : + AppCompatActivity(), + DIAware { private var inValidCount: Int = 0 private var isWithLogin = false @@ -108,7 +112,7 @@ class LoginActivity : AppCompatActivity(), DIAware { repository.updateApiInformation() ACRA.errorReporter.putCustomData( "SELFOSS_API_VERSION", - appSettingsService.getApiVersion().toString() + appSettingsService.getApiVersion().toString(), ) CountingIdlingResourceSingleton.decrement() } @@ -132,9 +136,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 +164,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) } @@ -205,7 +219,7 @@ class LoginActivity : AppCompatActivity(), DIAware { cancel = true binding.urlView.error = getString(R.string.login_url_problem) inValidCount++ - if (inValidCount == 3) { + if (inValidCount == MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED) { val alertDialog = AlertDialog.Builder(this).create() alertDialog.setTitle(getString(R.string.warning_wrong_url)) alertDialog.setMessage(getString(R.string.text_wrong_url)) @@ -270,7 +284,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 +294,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 +304,4 @@ class LoginActivity : AppCompatActivity(), DIAware { else -> super.onOptionsItemSelected(item) } } -} \ No newline at end of file +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt index b23b3ba..43d0879 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt @@ -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() with singleton { AppSettingsService(ACRA.isACRASenderServiceProcess() || TestingHelper().isUnitTest()) } import(networkModule) bind() with singleton { DriverFactory(applicationContext) } bind() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) } bind() with - singleton { - Repository( - instance(), - instance(), - isConnectionAvailable, - instance(), - ) - } + singleton { + Repository( + instance(), + instance(), + isConnectionAvailable, + instance(), + ) + } bind() with singleton { ConnectivityStatus(applicationContext) } bind() with singleton { AppViewModel(repository = instance()) } } @@ -60,6 +62,7 @@ class MyApp : MultiDexApplication(), DIAware { private val connectivityStatus: ConnectivityStatus by instance() private val driverFactory: DriverFactory by instance() + @Suppress("detekt:ForbiddenComment") // TODO: handle with the "previous" way private val isConnectionAvailable: MutableStateFlow = MutableStateFlow(true) @@ -89,11 +92,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 +155,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 +203,4 @@ class MyApp : MultiDexApplication(), DIAware { super.onPause(owner) } } -} \ No newline at end of file +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ReaderActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ReaderActivity.kt index a736528..055b974 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ReaderActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/ReaderActivity.kt @@ -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 @@ -51,6 +53,7 @@ class ReaderActivity : AppCompatActivity(), DIAware { showMenuItem(false) } + @Suppress("detekt:SwallowedException") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityReaderBinding.inflate(layoutInflater) @@ -99,8 +102,9 @@ 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]) @@ -109,25 +113,26 @@ class ReaderActivity : AppCompatActivity(), DIAware { 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 - currentFragment.scrollDown() + currentFragment.volumeButtonScrollDown() true } + KeyEvent.KEYCODE_VOLUME_UP -> { val currentFragment = supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment - currentFragment.scrollUp() + currentFragment.volumeButtonScrollUp() true } + else -> { super.onKeyDown(keyCode, event) } } - } private fun alignmentMenu() { val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT @@ -187,6 +192,7 @@ class ReaderActivity : AppCompatActivity(), DIAware { onBackPressedDispatcher.onBackPressed() return true } + R.id.star -> { if (allItems[binding.pager.currentItem].starred) { CoroutineScope(Dispatchers.IO).launch { @@ -200,10 +206,12 @@ class ReaderActivity : AppCompatActivity(), DIAware { afterSave() } } + R.id.align_left -> { switchAlignmentSetting(AppSettingsService.ALIGN_LEFT) refreshFragment() } + R.id.align_justify -> { switchAlignmentSetting(AppSettingsService.JUSTIFY) refreshFragment() diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt index f322627..70745e3 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt @@ -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)) } } -} \ No newline at end of file +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/UpsertSourceActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/UpsertSourceActivity.kt index a6b93d8..548aa6f 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/UpsertSourceActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/UpsertSourceActivity.kt @@ -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 @@ -83,6 +85,7 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware { } } + @Suppress("detekt:SwallowedException") private fun handleSpoutsSpinner() { val spoutsKV = HashMap() binding.spoutsSpinner.onItemSelectedListener = @@ -105,11 +108,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 } @@ -170,6 +174,7 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware { sourceDetailsUnavailable -> { Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show() } + else -> { CoroutineScope(Dispatchers.Main).launch { val successfullyAddedSource = @@ -192,11 +197,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() } } } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt index 3f2de89..894af9e 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt @@ -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) -} \ No newline at end of file + inner class ViewHolder( + val binding: CardItemBinding, + ) : RecyclerView.ViewHolder(binding.root) +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemListAdapter.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemListAdapter.kt index f6a48b5..a8e0256 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemListAdapter.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemListAdapter.kt @@ -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) -} \ No newline at end of file + inner class ViewHolder( + val binding: ListItemBinding, + ) : RecyclerView.ViewHolder(binding.root) +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemsAdapter.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemsAdapter.kt index e3ea18c..79391a6 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemsAdapter.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemsAdapter.kt @@ -18,7 +18,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.kodein.di.DIAware -abstract class ItemsAdapter : RecyclerView.Adapter(), DIAware { +abstract class ItemsAdapter : + RecyclerView.Adapter(), + DIAware { abstract val items: ArrayList abstract val repository: Repository abstract val binding: ViewBinding @@ -45,8 +47,7 @@ abstract class ItemsAdapter : 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 : 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 : 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( diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/SourcesListAdapter.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/SourcesListAdapter.kt index 069e227..1998cab 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/SourcesListAdapter.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/SourcesListAdapter.kt @@ -29,7 +29,8 @@ import org.kodein.di.instance class SourcesListAdapter( private val app: Activity, private val items: ArrayList, -) : RecyclerView.Adapter(), DIAware { +) : RecyclerView.Adapter(), + 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) } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/LoadingWorker.kt similarity index 81% rename from androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt rename to androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/LoadingWorker.kt index 8e0bc89..0d73f83 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/LoadingWorker.kt @@ -23,11 +23,15 @@ 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), +private const val NOTIFICATION_DELAY = 4000L + +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 +44,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,28 +92,27 @@ 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) - Timer("", false).schedule(4000) { + Timer("", false).schedule(NOTIFICATION_DELAY) { notificationManager.notify(2, newItemsNotification.build()) } } - Timer("", false).schedule(4000) { + Timer("", false).schedule(NOTIFICATION_DELAY) { notificationManager.cancel(1) } } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt index 84226de..705e7f1 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt @@ -1,5 +1,6 @@ package bou.amine.apps.readerforselfossv2.android.fragments +import android.content.Context import android.content.Intent import android.content.res.ColorStateList import android.content.res.TypedArray @@ -8,6 +9,7 @@ import android.graphics.Typeface import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.util.TypedValue +import android.util.TypedValue.DATA_NULL_UNDEFINED import android.view.GestureDetector import android.view.InflateException import android.view.LayoutInflater @@ -64,8 +66,14 @@ import java.util.concurrent.ExecutionException private const val IMAGE_JPG = "image/jpg" -class ArticleFragment : Fragment(), DIAware { - private var fontSize: Int = 16 +private const val WHITE_COLOR_HEX = 0xFFFFFF + +private const val DEFAULT_FONT_SIZE = 16 + +class ArticleFragment : + Fragment(), + DIAware { + private var fontSize: Int = DEFAULT_FONT_SIZE private lateinit var item: SelfossModel.Item private lateinit var url: String private lateinit var contentText: String @@ -96,6 +104,7 @@ class ArticleFragment : Fragment(), DIAware { item = pi.toModel() } + @Suppress("detekt:LongMethod") override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -113,12 +122,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() @@ -164,7 +174,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( @@ -172,8 +183,7 @@ class ArticleFragment : Fragment(), DIAware { ) { _, _ -> appSettingsService.disableArticleViewer() requireActivity().finish() - } - .create() + }.create() .show() } catch (e: IllegalStateException) { e.sendSilentlyWithAcraWithName("Context required is null") @@ -232,21 +242,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") @@ -273,6 +285,7 @@ class ArticleFragment : Fragment(), DIAware { } } + @Suppress("detekt:SwallowedException") private fun getContentFromMercury() { binding.progressBar.visibility = View.VISIBLE @@ -311,16 +324,15 @@ class ArticleFragment : Fragment(), DIAware { } } - private fun handleLeadImage(lead_image_url: String?) { - if (!lead_image_url.isNullOrEmpty() && context != null) { + private fun handleLeadImage(leadImageUrl: String?) { + if (!leadImageUrl.isNullOrEmpty() && context != null) { binding.imageView.visibility = View.VISIBLE Glide .with(requireContext()) .asBitmap() .load( - lead_image_url, - ) - .apply(RequestOptions.fitCenterTransform()) + leadImageUrl, + ).apply(RequestOptions.fitCenterTransform()) .into(binding.imageView) } else { binding.imageView.visibility = View.GONE @@ -334,84 +346,133 @@ 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 } - } + @Suppress("detekt:LongMethod", "detekt:SwallowedException") @Deprecated("Deprecated in Java") override fun shouldInterceptRequest( view: WebView, url: String, ): WebResourceResponse? { val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL) + var glideResource: WebResourceResponse? = null 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, - "UTF-8", - getBitmapInputStream(image, Bitmap.CompressFormat.JPEG), - ) + glideResource = + WebResourceResponse( + IMAGE_JPG, + "UTF-8", + getBitmapInputStream(image, Bitmap.CompressFormat.JPEG), + ) } catch (e: ExecutionException) { // Do nothing } } else if (url.lowercase(Locale.US).contains(".png")) { try { val image = - Glide.with(view).asBitmap().apply(glideOptions).load(url).submit() + Glide + .with(view) + .asBitmap() + .apply(glideOptions) + .load(url) + .submit() .get() - return WebResourceResponse( - IMAGE_JPG, - "UTF-8", - getBitmapInputStream(image, Bitmap.CompressFormat.PNG), - ) + glideResource = + WebResourceResponse( + IMAGE_JPG, + "UTF-8", + getBitmapInputStream(image, Bitmap.CompressFormat.PNG), + ) } catch (e: ExecutionException) { // Do nothing } } else if (url.lowercase(Locale.US).contains(".webp")) { try { val image = - Glide.with(view).asBitmap().apply(glideOptions).load(url).submit() + Glide + .with(view) + .asBitmap() + .apply(glideOptions) + .load(url) + .submit() .get() - return WebResourceResponse( - IMAGE_JPG, - "UTF-8", - getBitmapInputStream(image, Bitmap.CompressFormat.WEBP), - ) + glideResource = + WebResourceResponse( + IMAGE_JPG, + "UTF-8", + getBitmapInputStream(image, Bitmap.CompressFormat.WEBP), + ) } catch (e: ExecutionException) { // Do nothing } } - return super.shouldInterceptRequest(view, url) + return glideResource ?: super.shouldInterceptRequest(view, url) } } } + @Suppress("detekt:LongMethod", "detekt:ImplicitDefaultLocale") private fun htmlToWebview() { + val context: Context + try { + context = requireContext() + } catch (e: IllegalStateException) { + e.sendSilentlyWithAcraWithName("Context required is null") + return + } + + val colorOnSurface = TypedValue() + val colorSurface = TypedValue() + try { val attrs: IntArray = intArrayOf(android.R.attr.fontFamily) - val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs) + val a: TypedArray = context.obtainStyledAttributes(resId, attrs) binding.webcontent.settings.standardFontFamily = a.getString(0) binding.webcontent.visibility = View.VISIBLE - val colorOnSurface = TypedValue() - requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true) + context.theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true) - val colorSurface = TypedValue() - requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, 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") + } + val colorSurfaceString = + String.format( + "#%06X", + WHITE_COLOR_HEX and (if (colorSurface.data != DATA_NULL_UNDEFINED) colorSurface.data else WHITE_COLOR_HEX), + ) + + val colorOnSurfaceString = + String.format( + "#%06X", + WHITE_COLOR_HEX and (if (colorOnSurface.data != DATA_NULL_UNDEFINED) colorOnSurface.data else 0), + ) + + try { binding.webcontent.settings.useWideViewPort = true binding.webcontent.settings.loadWithOverviewMode = true binding.webcontent.settings.javaScriptEnabled = false @@ -422,19 +483,25 @@ 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) } + binding.webcontent.setOnTouchListener { _, event -> + gestureDetector.onTouchEvent( + event, + ) + } binding.webcontent.settings.layoutAlgorithm = WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING + } catch (e: IllegalStateException) { + e.sendSilentlyWithAcraWithName("Context is null but wasn't, and that's causing issues with webview config") + return + } + try { var baseUrl: String? = null - try { val itemUrl = URL(url) baseUrl = itemUrl.protocol + "://" + itemUrl.host @@ -484,12 +551,12 @@ class ArticleFragment : Fragment(), DIAware { | color: ${ String.format( "#%06X", - 0xFFFFFF and resources.getColor(R.color.colorAccent), + WHITE_COLOR_HEX and context.resources.getColor(R.color.colorAccent), ) } !important; | } | *:not(a) { - | color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)}; + | color: $colorOnSurfaceString; | } | * { | font-size: ${fontSize}px; @@ -497,26 +564,11 @@ class ArticleFragment : Fragment(), DIAware { | word-break: break-word; | overflow:hidden; | line-height: 1.5em; - | background-color: ${ - String.format( - "#%06X", - 0xFFFFFF and colorSurface.data, - ) - }; + | background-color: $colorSurfaceString; | } | body, html { - | background-color: ${ - String.format( - "#%06X", - 0xFFFFFF and colorSurface.data, - ) - } !important; - | border-color: ${ - String.format( - "#%06X", - 0xFFFFFF and colorSurface.data, - ) - } !important; + | background-color: $colorSurfaceString !important; + | border-color: $colorSurfaceString !important; | padding: 0 !important; | margin: 0 !important; | } @@ -526,12 +578,7 @@ class ArticleFragment : Fragment(), DIAware { | pre, code { | white-space: pre-wrap; | width:100%; - | background-color: ${ - String.format( - "#%06X", - 0xFFFFFF and colorSurface.data, - ) - }; + | background-color: $colorSurfaceString; | } | | $fontLinkAndStyle @@ -545,16 +592,16 @@ class ArticleFragment : Fragment(), DIAware { null, ) } catch (e: IllegalStateException) { - e.sendSilentlyWithAcraWithName("Context required is null") + e.sendSilentlyWithAcraWithName("Context required is still null ?") } } - fun scrollDown() { + fun volumeButtonScrollDown() { val height = binding.nestedScrollView.measuredHeight binding.nestedScrollView.smoothScrollBy(0, height / 2) } - fun scrollUp() { + fun volumeButtonScrollUp() { val height = binding.nestedScrollView.measuredHeight binding.nestedScrollView.smoothScrollBy(0, -height / 2) } @@ -581,10 +628,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) @@ -596,4 +644,4 @@ class ArticleFragment : Fragment(), DIAware { } return false } -} \ No newline at end of file +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/FilterSheetFragment.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/FilterSheetFragment.kt index 7af823f..bab0d24 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/FilterSheetFragment.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/FilterSheetFragment.kt @@ -33,7 +33,11 @@ import org.kodein.di.DIAware import org.kodein.di.android.x.closestDI import org.kodein.di.instance -class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { +private const val DRAWABLE_SIZE = 30 + +class FilterSheetFragment : + BottomSheetDialogFragment(), + DIAware { private lateinit var binding: FilterFragmentBinding override val di: DI by closestDI() private val repository: Repository by instance() @@ -80,7 +84,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(c) { @@ -153,8 +158,8 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { } gd.setColor(gdColor) gd.shape = GradientDrawable.RECTANGLE - gd.setSize(30, 30) - gd.cornerRadius = 30F + gd.setSize(DRAWABLE_SIZE, DRAWABLE_SIZE) + gd.cornerRadius = DRAWABLE_SIZE.toFloat() c.chipIcon = gd } catch (e: Exception) { e.sendSilentlyWithAcraWithName("tags > GradientDrawable") @@ -190,4 +195,4 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { companion object { const val TAG = "FilterModalBottomSheet" } -} \ No newline at end of file +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ImageFragment.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ImageFragment.kt index b9c68e0..3a4e023 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ImageFragment.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ImageFragment.kt @@ -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) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/model/AndroidIModelUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/model/AndroidIModelUtils.kt index 2898e9f..426f537 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/model/AndroidIModelUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/model/AndroidIModelUtils.kt @@ -9,17 +9,22 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.request.RequestOptions +private const val PRELOAD_IMAGE_TIMEOUT = 10000 + fun SelfossModel.Item.preloadImages(context: Context): Boolean { val imageUrls = this.getImages() - val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000) + val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(PRELOAD_IMAGE_TIMEOUT) 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 +45,4 @@ fun String.toTextDrawableString(): String { } } return textDrawable.toString() -} \ No newline at end of file +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/settings/SettingsActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/settings/SettingsActivity.kt index 0ea06be..7d2fb3f 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/settings/SettingsActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/settings/SettingsActivity.kt @@ -17,12 +17,19 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBin import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser import bou.amine.apps.readerforselfossv2.service.AppSettingsService +import bou.amine.apps.readerforselfossv2.service.AppSettingsService.Companion.API_ITEMS_NUMBER +import bou.amine.apps.readerforselfossv2.service.AppSettingsService.Companion.CURRENT_THEME +import bou.amine.apps.readerforselfossv2.service.AppSettingsService.Companion.READER_FONT_SIZE import com.mikepenz.aboutlibraries.LibsBuilder import org.kodein.di.DIAware import org.kodein.di.android.closestDI private const val TITLE_TAG = "settingsActivityTitle" +const val MAX_ITEMS_NUMBER = 200 + +private const val MIN_ITEMS_NUMBER = 1 + class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, @@ -61,15 +68,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, @@ -78,15 +84,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() @@ -102,10 +110,10 @@ class SettingsActivity : ) { setPreferencesFromResource(R.xml.pref_main, rootKey) - preferenceManager.findPreference("currentMode")?.onPreferenceChangeListener = + preferenceManager.findPreference(CURRENT_THEME)?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> AppCompatDelegate.setDefaultNightMode( - newValue.toString().toInt() + newValue.toString().toInt(), ) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯ true } @@ -131,7 +139,7 @@ class SettingsActivity : setPreferencesFromResource(R.xml.pref_general, rootKey) val editTextPreference = - preferenceManager.findPreference("prefer_api_items_number") + preferenceManager.findPreference(API_ITEMS_NUMBER) editTextPreference?.setOnBindEditTextListener { editText -> editText.inputType = InputType.TYPE_CLASS_NUMBER editText.filters = @@ -139,13 +147,14 @@ class SettingsActivity : InputFilter { source, _, _, dest, _, _ -> try { val input: Int = (dest.toString() + source.toString()).toInt() - if (input in 1..200) return@InputFilter null + if (input in MIN_ITEMS_NUMBER..MAX_ITEMS_NUMBER) return@InputFilter null } catch (nfe: NumberFormatException) { - 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() } "" }, @@ -161,7 +170,7 @@ class SettingsActivity : ) { setPreferencesFromResource(R.xml.pref_viewer, rootKey) - val fontSize = preferenceManager.findPreference("reader_font_size") + val fontSize = preferenceManager.findPreference(READER_FONT_SIZE) fontSize?.setOnBindEditTextListener { editText -> editText.inputType = InputType.TYPE_CLASS_NUMBER editText.addTextChangedListener { @@ -218,23 +227,6 @@ class SettingsActivity : } } - class ThemePreferenceFragment : PreferenceFragmentCompat() { - override fun onCreatePreferences( - savedInstanceState: Bundle?, - rootKey: String?, - ) { - setPreferencesFromResource(R.xml.pref_theme, rootKey) - - preferenceManager.findPreference("currentMode")?.onPreferenceChangeListener = - Preference.OnPreferenceChangeListener { _, newValue -> - AppCompatDelegate.setDefaultNightMode( - newValue.toString().toInt() - ) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯ - true - } - } - } - class LinksPreferenceFragment : PreferenceFragmentCompat() { private fun openUrl(url: String) { context?.openUrlInBrowser(url) @@ -248,19 +240,19 @@ class SettingsActivity : preferenceManager.findPreference("trackerLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { - openUrl(AppSettingsService.trackerUrl) + openUrl(AppSettingsService.BUG_URL) true } preferenceManager.findPreference("sourceLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { - openUrl(AppSettingsService.sourceUrl) + openUrl(AppSettingsService.SOURCE_URL) false } preferenceManager.findPreference("translation")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { - openUrl(AppSettingsService.translationUrl) + openUrl(AppSettingsService.TRANSLATION_URL) false } } @@ -274,4 +266,4 @@ class SettingsActivity : setPreferencesFromResource(R.xml.pref_experimental, rootKey) } } -} \ No newline at end of file +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/testing/CountingIdlingResourceSingleton.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/testing/CountingIdlingResourceSingleton.kt index 089ba63..b134a83 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/testing/CountingIdlingResourceSingleton.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/testing/CountingIdlingResourceSingleton.kt @@ -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() } } -} \ No newline at end of file +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/testing/TestingHelper.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/testing/TestingHelper.kt index 897465c..56abc2a 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/testing/TestingHelper.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/testing/TestingHelper.kt @@ -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" } -} \ No newline at end of file +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/AppUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/AppUtils.kt index f67830d..4bbeaa5 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/AppUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/AppUtils.kt @@ -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), ) } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/CircleImageView.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/CircleImageView.kt index 31ff749..c6d227d 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/CircleImageView.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/CircleImageView.kt @@ -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] } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/LinksUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/LinksUtils.kt index ddc4752..1df697f 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/LinksUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/LinksUtils.kt @@ -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) @@ -74,13 +72,13 @@ fun Context.openUrlInBrowser(url: String) { this.mayBeStartActivity(intent) } +@Suppress("detekt:SwallowedException") fun Context.mayBeStartActivity(intent: Intent) { try { this.startActivity(intent) } catch (e: ActivityNotFoundException) { Toast.makeText(this, getString(R.string.no_browser), Toast.LENGTH_SHORT).show() } - } class LinkOnTouchListener : View.OnTouchListener { @@ -122,4 +120,4 @@ class LinkOnTouchListener : View.OnTouchListener { } return ret } -} \ No newline at end of file +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/acra/ACRAUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/acra/ACRAUtils.kt index b6e6199..63751a3 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/acra/ACRAUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/acra/ACRAUtils.kt @@ -6,4 +6,4 @@ import org.acra.ktx.sendSilentlyWithAcra fun Throwable.sendSilentlyWithAcraWithName(name: String) { ACRA.errorReporter.putCustomData("error_source", name) this.sendSilentlyWithAcra() -} \ No newline at end of file +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/acra/AcraReportingAdministrator.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/acra/AcraReportingAdministrator.kt index 0ac5463..0ece4fb 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/acra/AcraReportingAdministrator.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/acra/AcraReportingAdministrator.kt @@ -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" - } -} \ No newline at end of file + crashReportData: CrashReportData, + ): Boolean = crashReportData.get("BRAND") != "redroid" +} diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/glide/GlideUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/glide/GlideUtils.kt index 67b8906..bdb56bf 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/glide/GlideUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/glide/GlideUtils.kt @@ -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,17 +26,20 @@ fun Context.circularDrawable( ) { view.textView.text = "" - Glide.with(this) + Glide + .with(this) .load(url) .into(view.imageView) } +private const val BITMAP_INPUT_STREAM_COMPRESSION_QUALITY = 80 + fun getBitmapInputStream( bitmap: Bitmap, compressFormat: Bitmap.CompressFormat, ): InputStream { val byteArrayOutputStream = ByteArrayOutputStream() - bitmap.compress(compressFormat, 80, byteArrayOutputStream) + bitmap.compress(compressFormat, BITMAP_INPUT_STREAM_COMPRESSION_QUALITY, byteArrayOutputStream) val bitmapData: ByteArray = byteArrayOutputStream.toByteArray() return ByteArrayInputStream(bitmapData) } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt index ed5f788..b7bfe0a 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt @@ -3,7 +3,6 @@ package bou.amine.apps.readerforselfossv2.android.utils.network import android.content.Context import android.net.ConnectivityManager import android.net.NetworkCapabilities -import android.os.Build import com.google.android.material.snackbar.Snackbar lateinit var s: Snackbar @@ -11,19 +10,13 @@ lateinit var s: Snackbar fun isNetworkAccessible(context: Context): Boolean { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val network = connectivityManager.activeNetwork ?: return false - val networkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false + val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) ?: return false - return when { - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true - else -> false - } - } else { - val network = connectivityManager.activeNetworkInfo ?: return false - return network.isConnectedOrConnecting + return when { + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true + else -> false } } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt index 7cc0a81..2b73e01 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt @@ -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() 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 + } } } } diff --git a/androidApp/src/main/res/xml/pref_theme.xml b/androidApp/src/main/res/xml/pref_theme.xml deleted file mode 100644 index 5a6ea0b..0000000 --- a/androidApp/src/main/res/xml/pref_theme.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - \ No newline at end of file diff --git a/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/helpers.kt b/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/Helpers.kt similarity index 85% rename from androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/helpers.kt rename to androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/Helpers.kt index 319538f..51ebb26 100644 --- a/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/helpers.kt +++ b/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/Helpers.kt @@ -11,13 +11,17 @@ fun dialogMessage(): String { return latestDialog.findViewById(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) -} \ No newline at end of file +} diff --git a/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/LoginActivityTest.kt b/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/LoginActivityTest.kt index 2dd513c..5eba600 100644 --- a/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/LoginActivityTest.kt +++ b/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/LoginActivityTest.kt @@ -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) } }*/ -} \ No newline at end of file +} diff --git a/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/RobotElectriqueRunner.kt b/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/RobotElectriqueRunner.kt index 315dff2..2f22623 100644 --- a/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/RobotElectriqueRunner.kt +++ b/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/android/tests/robolectric/RobotElectriqueRunner.kt @@ -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() - } -} \ No newline at end of file +class RobotElectriqueRunner( + testClass: Class<*>?, +) : RobolectricTestRunner(testClass) { + override fun buildGlobalConfig(): Config = Config.Builder().setSdk(25, 30, 33).build() +} diff --git a/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/tests/repository/RepositoryTest.kt b/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/tests/repository/RepositoryTest.kt index fc78ef4..3eebe02 100644 --- a/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/tests/repository/RepositoryTest.kt +++ b/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/tests/repository/RepositoryTest.kt @@ -1,3 +1,5 @@ +@file:Suppress("detekt:LargeClass") + package bou.amine.apps.readerforselfossv2.tests.repository import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB @@ -42,14 +44,14 @@ private const val FEED_URL = "https://test.com/feed" private const val TAGS = "Test, New" +private const val NUMBER_ARTICLES = 100 +private const val NUMBER_UNREAD = 50 +private const val NUMBER_STARRED = 20 + class RepositoryTest { private val db = mockk(relaxed = true) private val appSettingsService = mockk() private val api = mockk() - - 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 +77,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 +120,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 +136,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 +157,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 +178,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 +199,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 +223,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 +239,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 +254,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 +270,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 +309,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 +337,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 +367,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 +383,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 +400,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 +840,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 +868,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 +896,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 +969,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 +987,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 +1005,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 +1023,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 +1116,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 +1134,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 +1148,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 +1175,4 @@ class RepositoryTest { ) repository.searchFilter = "search" } -} \ No newline at end of file +} diff --git a/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/tests/repository/TestUtils.kt b/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/tests/repository/TestUtils.kt index ca36a2d..e1d83c9 100644 --- a/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/tests/repository/TestUtils.kt +++ b/androidApp/src/test/kotlin/bou/amine/apps/readerforselfossv2/tests/repository/TestUtils.kt @@ -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 { - return listOf( +fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List = + listOf( ITEM( id = item.id, datetime = item.datetime, @@ -20,10 +20,9 @@ fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List { - return listOf( +fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List = + listOf( SelfossModel.Item( id = item.id.toInt(), datetime = item.datetime, @@ -39,7 +38,6 @@ fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List.*>[my_property]' + excludes: '' + +processors: + active: true + exclude: + - 'DetektProgressListener' + # - 'KtFileCountProcessor' + # - 'PackageCountProcessor' + # - 'ClassCountProcessor' + # - 'FunctionCountProcessor' + # - 'PropertyCountProcessor' + # - 'ProjectComplexityProcessor' + # - 'ProjectCognitiveComplexityProcessor' + # - 'ProjectLLOCProcessor' + # - 'ProjectCLOCProcessor' + # - 'ProjectLOCProcessor' + # - 'ProjectSLOCProcessor' + # - 'LicenseHeaderLoaderExtension' + +console-reports: + active: true + exclude: + - 'ProjectStatisticsReport' + - 'ComplexityReport' + - 'NotificationReport' + - 'FindingsReport' + - 'FileBasedFindingsReport' + # - 'LiteFindingsReport' + +output-reports: + active: true + exclude: + # - 'TxtOutputReport' + # - 'XmlOutputReport' + # - 'HtmlOutputReport' + # - 'MdOutputReport' + # - 'SarifOutputReport' + +comments: + active: true + AbsentOrWrongFileLicense: + active: false + licenseTemplateFile: 'license.template' + licenseTemplateIsRegex: false + CommentOverPrivateFunction: + active: false + CommentOverPrivateProperty: + active: false + DeprecatedBlockTag: + active: false + EndOfSentenceFormat: + active: false + endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' + KDocReferencesNonPublicProperty: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + OutdatedDocumentation: + active: false + matchTypeParameters: true + matchDeclarationsOrder: true + allowParamOnConstructorProperties: false + UndocumentedPublicClass: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + searchInNestedClass: true + searchInInnerClass: true + searchInInnerObject: true + searchInInnerInterface: true + searchInProtectedClass: false + UndocumentedPublicFunction: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + searchProtectedFunction: false + UndocumentedPublicProperty: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + searchProtectedProperty: false + +complexity: + active: true + CognitiveComplexMethod: + active: false + threshold: 15 + ComplexCondition: + active: true + threshold: 4 + ComplexInterface: + active: false + threshold: 10 + includeStaticDeclarations: false + includePrivateDeclarations: false + ignoreOverloaded: false + CyclomaticComplexMethod: + active: true + threshold: 15 + ignoreSingleWhenExpression: false + ignoreSimpleWhenEntries: false + ignoreNestingFunctions: false + nestingFunctions: + - 'also' + - 'apply' + - 'forEach' + - 'isNotNull' + - 'ifNull' + - 'let' + - 'run' + - 'use' + - 'with' + LabeledExpression: + active: false + ignoredLabels: [ ] + LargeClass: + active: true + threshold: 600 + LongMethod: + active: true + threshold: 60 + LongParameterList: + active: true + functionThreshold: 6 + constructorThreshold: 7 + ignoreDefaultParameters: false + ignoreDataClasses: true + ignoreAnnotatedParameter: [ ] + MethodOverloading: + active: false + threshold: 6 + NamedArguments: + active: false + threshold: 3 + ignoreArgumentsMatchingNames: false + NestedBlockDepth: + active: true + threshold: 4 + NestedScopeFunctions: + active: false + threshold: 1 + functions: + - 'kotlin.apply' + - 'kotlin.run' + - 'kotlin.with' + - 'kotlin.let' + - 'kotlin.also' + ReplaceSafeCallChainWithRun: + active: false + StringLiteralDuplication: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + threshold: 3 + ignoreAnnotation: true + excludeStringsWithLessThan5Characters: true + ignoreStringsRegex: '$^' + TooManyFunctions: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/android/*Activity.kt', '**/fragments/*Fragment.kt' ] + thresholdInFiles: 11 + thresholdInClasses: 11 + thresholdInInterfaces: 11 + thresholdInObjects: 11 + thresholdInEnums: 11 + ignoreDeprecated: false + ignorePrivate: false + ignoreOverridden: false + ignoreAnnotatedFunctions: [ ] + +coroutines: + active: true + GlobalCoroutineUsage: + active: false + InjectDispatcher: + active: true + dispatcherNames: + - 'IO' + - 'Default' + - 'Unconfined' + RedundantSuspendModifier: + active: true + SleepInsteadOfDelay: + active: true + SuspendFunSwallowedCancellation: + active: false + SuspendFunWithCoroutineScopeReceiver: + active: false + SuspendFunWithFlowReturnType: + active: true + +empty-blocks: + active: true + EmptyCatchBlock: + active: true + allowedExceptionNameRegex: '_|(ignore|expected).*' + EmptyClassBlock: + active: true + EmptyDefaultConstructor: + active: true + EmptyDoWhileBlock: + active: true + EmptyElseBlock: + active: true + EmptyFinallyBlock: + active: true + EmptyForBlock: + active: true + EmptyFunctionBlock: + active: true + ignoreOverridden: false + EmptyIfBlock: + active: true + EmptyInitBlock: + active: true + EmptyKtFile: + active: true + EmptySecondaryConstructor: + active: true + EmptyTryBlock: + active: true + EmptyWhenBlock: + active: true + EmptyWhileBlock: + active: true + +exceptions: + active: true + ExceptionRaisedInUnexpectedLocation: + active: true + methodNames: + - 'equals' + - 'finalize' + - 'hashCode' + - 'toString' + InstanceOfCheckForException: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + NotImplementedDeclaration: + active: false + ObjectExtendsThrowable: + active: false + PrintStackTrace: + active: true + RethrowCaughtException: + active: true + ReturnFromFinally: + active: true + ignoreLabeled: false + SwallowedException: + active: true + ignoredExceptionTypes: + - 'InterruptedException' + - 'MalformedURLException' + - 'NumberFormatException' + - 'ParseException' + allowedExceptionNameRegex: '_|(ignore|expected).*' + ThrowingExceptionFromFinally: + active: true + ThrowingExceptionInMain: + active: false + ThrowingExceptionsWithoutMessageOrCause: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + exceptions: + - 'ArrayIndexOutOfBoundsException' + - 'Exception' + - 'IllegalArgumentException' + - 'IllegalMonitorStateException' + - 'IllegalStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + ThrowingNewInstanceOfSameException: + active: true + TooGenericExceptionCaught: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + exceptionNames: + - 'ArrayIndexOutOfBoundsException' + - 'Error' + - 'Exception' + - 'IllegalMonitorStateException' + - 'IndexOutOfBoundsException' + - 'NullPointerException' + - 'RuntimeException' + - 'Throwable' + allowedExceptionNameRegex: '_|(ignore|expected).*' + TooGenericExceptionThrown: + active: true + exceptionNames: + - 'Error' + - 'Exception' + - 'RuntimeException' + - 'Throwable' + +naming: + active: true + BooleanPropertyNaming: + active: false + allowedPattern: '^(is|has|are)' + ClassNaming: + active: true + classPattern: '[A-Z][a-zA-Z0-9]*' + ConstructorParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + privateParameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + EnumNaming: + active: true + enumEntryPattern: '[A-Z][_a-zA-Z0-9]*' + ForbiddenClassName: + active: false + forbiddenName: [ ] + FunctionMaxLength: + active: false + maximumFunctionNameLength: 30 + FunctionMinLength: + active: false + minimumFunctionNameLength: 3 + FunctionNaming: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + functionPattern: '[a-z][a-zA-Z0-9]*' + excludeClassPattern: '$^' + FunctionParameterNaming: + active: true + parameterPattern: '[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + InvalidPackageDeclaration: + active: true + rootPackage: '' + requireRootInDeclaration: false + LambdaParameterNaming: + active: false + parameterPattern: '[a-z][A-Za-z0-9]*|_' + MatchingDeclarationName: + active: false # done in ktlint + mustBeFirst: true + MemberNameEqualsClassName: + active: true + ignoreOverridden: true + NoNameShadowing: + active: true + NonBooleanPropertyPrefixedWithIs: + active: false + ObjectPropertyNaming: + active: true + constantPattern: '[A-Za-z][_A-Za-z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' + PackageNaming: + active: true + packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*' + TopLevelPropertyNaming: + active: true + constantPattern: '[A-Z][_A-Z0-9]*' + propertyPattern: '[A-Za-z][_A-Za-z0-9]*' + privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' + VariableMaxLength: + active: false + maximumVariableNameLength: 64 + VariableMinLength: + active: false + minimumVariableNameLength: 1 + VariableNaming: + active: true + variablePattern: '[a-z][A-Za-z0-9]*' + privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' + excludeClassPattern: '$^' + +performance: + active: true + ArrayPrimitive: + active: true + CouldBeSequence: + active: false + threshold: 3 + ForEachOnRange: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + SpreadOperator: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + UnnecessaryPartOfBinaryExpression: + active: false + UnnecessaryTemporaryInstantiation: + active: true + +potential-bugs: + active: true + AvoidReferentialEquality: + active: true + forbiddenTypePatterns: + - 'kotlin.String' + CastNullableToNonNullableType: + active: false + CastToNullableType: + active: false + Deprecation: + active: false + DontDowncastCollectionTypes: + active: false + DoubleMutabilityForCollection: + active: true + mutableTypes: + - 'kotlin.collections.MutableList' + - 'kotlin.collections.MutableMap' + - 'kotlin.collections.MutableSet' + - 'java.util.ArrayList' + - 'java.util.LinkedHashSet' + - 'java.util.HashSet' + - 'java.util.LinkedHashMap' + - 'java.util.HashMap' + ElseCaseInsteadOfExhaustiveWhen: + active: false + ignoredSubjectTypes: [ ] + EqualsAlwaysReturnsTrueOrFalse: + active: true + EqualsWithHashCodeExist: + active: true + ExitOutsideMain: + active: false + ExplicitGarbageCollectionCall: + active: true + HasPlatformType: + active: true + IgnoredReturnValue: + active: true + restrictToConfig: true + returnValueAnnotations: + - 'CheckResult' + - '*.CheckResult' + - 'CheckReturnValue' + - '*.CheckReturnValue' + ignoreReturnValueAnnotations: + - 'CanIgnoreReturnValue' + - '*.CanIgnoreReturnValue' + returnValueTypes: + - 'kotlin.sequences.Sequence' + - 'kotlinx.coroutines.flow.*Flow' + - 'java.util.stream.*Stream' + ignoreFunctionCall: [ ] + ImplicitDefaultLocale: + active: true + ImplicitUnitReturnType: + active: false + allowExplicitReturnType: true + InvalidRange: + active: true + IteratorHasNextCallsNextMethod: + active: true + IteratorNotThrowingNoSuchElementException: + active: true + LateinitUsage: + active: false + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + ignoreOnClassesPattern: '' + MapGetWithNotNullAssertionOperator: + active: true + MissingPackageDeclaration: + active: false + excludes: [ '**/*.kts' ] + NullCheckOnMutableProperty: + active: false + NullableToStringCall: + active: false + PropertyUsedBeforeDeclaration: + active: false + UnconditionalJumpStatementInLoop: + active: false + UnnecessaryNotNullCheck: + active: false + UnnecessaryNotNullOperator: + active: true + UnnecessarySafeCall: + active: true + UnreachableCatchBlock: + active: true + UnreachableCode: + active: true + UnsafeCallOnNullableType: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ] + UnsafeCast: + active: true + UnusedUnaryOperator: + active: true + UselessPostfixExpression: + active: true + WrongEqualsTypeParameter: + active: true + +style: + active: true + AlsoCouldBeApply: + active: false + BracesOnIfStatements: + active: false + singleLine: 'never' + multiLine: 'always' + BracesOnWhenStatements: + active: false + singleLine: 'necessary' + multiLine: 'consistent' + CanBeNonNullable: + active: false + CascadingCallWrapping: + active: false + includeElvis: true + ClassOrdering: + active: false + CollapsibleIfStatements: + active: false + DataClassContainsFunctions: + active: false + conversionFunctionPrefix: + - 'to' + allowOperators: false + DataClassShouldBeImmutable: + active: false + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 3 + DoubleNegativeLambda: + active: false + negativeFunctions: + - reason: 'Use `takeIf` instead.' + value: 'takeUnless' + - reason: 'Use `all` instead.' + value: 'none' + negativeFunctionNameParts: + - 'not' + - 'non' + EqualsNullCall: + active: true + EqualsOnSignatureLine: + active: false + ExplicitCollectionElementAccessMethod: + active: false + ExplicitItLambdaParameter: + active: true + ExpressionBodySyntax: + active: false + includeLineWrapping: false + ForbiddenAnnotation: + active: false + annotations: + - reason: 'it is a java annotation. Use `Suppress` instead.' + value: 'java.lang.SuppressWarnings' + - reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.' + value: 'java.lang.Deprecated' + - reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.' + value: 'java.lang.annotation.Documented' + - reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.' + value: 'java.lang.annotation.Target' + - reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.' + value: 'java.lang.annotation.Retention' + - reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.' + value: 'java.lang.annotation.Repeatable' + - reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265' + value: 'java.lang.annotation.Inherited' + ForbiddenComment: + active: true + comments: + - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' + value: 'FIXME:' + - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' + value: 'STOPSHIP:' + - reason: 'Forbidden TODO todo marker in comment, please do the changes.' + value: 'TODO:' + allowedPatterns: '' + ForbiddenImport: + active: false + imports: [ ] + forbiddenPatterns: '' + ForbiddenMethodCall: + active: false + methods: + - reason: 'print does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.print' + - reason: 'println does not allow you to configure the output stream. Use a logger instead.' + value: 'kotlin.io.println' + ForbiddenSuppress: + active: false + rules: [ ] + ForbiddenVoid: + active: true + ignoreOverridden: false + ignoreUsageInGenerics: false + FunctionOnlyReturningConstant: + active: true + ignoreOverridableFunction: true + ignoreActualFunction: true + excludedFunctions: [ ] + LoopWithTooManyJumpStatements: + active: true + maxJumpCount: 1 + MagicNumber: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts' ] + ignoreNumbers: + - '-1' + - '0' + - '1' + - '2' + ignoreHashCodeFunction: true + ignorePropertyDeclaration: false + ignoreLocalVariableDeclaration: false + ignoreConstantDeclaration: true + ignoreCompanionObjectPropertyDeclaration: true + ignoreAnnotation: false + ignoreNamedArgument: true + ignoreEnums: false + ignoreRanges: false + ignoreExtensionFunctions: true + MandatoryBracesLoops: + active: false + MaxChainedCallsOnSameLine: + active: false + maxChainedCalls: 5 + MaxLineLength: + active: false # done in ktlint + maxLineLength: 140 # default is 120. 140 to match ktlint + excludePackageStatements: true + excludeImportStatements: true + excludeCommentStatements: false + excludeRawStrings: true + MayBeConst: + active: true + ModifierOrder: + active: true + MultilineLambdaItParameter: + active: false + MultilineRawStringIndentation: + active: false + indentSize: 4 + trimmingMethods: + - 'trimIndent' + - 'trimMargin' + NestedClassesVisibility: + active: true + NewLineAtEndOfFile: + active: false # done in ktlint + NoTabs: + active: false + NullableBooleanCheck: + active: false + ObjectLiteralToLambda: + active: true + OptionalAbstractKeyword: + active: true + OptionalUnit: + active: false + PreferToOverPairSyntax: + active: false + ProtectedMemberInFinalClass: + active: true + RedundantExplicitType: + active: false + RedundantHigherOrderMapUsage: + active: true + RedundantVisibilityModifierRule: + active: false + ReturnCount: + active: true + max: 2 + excludedFunctions: + - 'equals' + excludeLabeled: false + excludeReturnFromLambda: true + excludeGuardClauses: false + SafeCast: + active: true + SerialVersionUIDInSerializableClass: + active: true + SpacingBetweenPackageAndImports: + active: false + StringShouldBeRawString: + active: false + maxEscapedCharacterCount: 2 + ignoredCharacters: [ ] + ThrowsCount: + active: true + max: 2 + excludeGuardClauses: false + TrailingWhitespace: + active: false + TrimMultilineRawString: + active: false + trimmingMethods: + - 'trimIndent' + - 'trimMargin' + UnderscoresInNumericLiterals: + active: false + acceptableLength: 4 + allowNonStandardGrouping: false + UnnecessaryAbstractClass: + active: true + UnnecessaryAnnotationUseSiteTarget: + active: false + UnnecessaryApply: + active: true + UnnecessaryBackticks: + active: false + UnnecessaryBracesAroundTrailingLambda: + active: false + UnnecessaryFilter: + active: true + UnnecessaryInheritance: + active: true + UnnecessaryInnerClass: + active: false + UnnecessaryLet: + active: false + UnnecessaryParentheses: + active: false + allowForUnclearPrecedence: false + UntilInsteadOfRangeTo: + active: false + UnusedImports: + active: false + UnusedParameter: + active: true + allowedNames: 'ignored|expected' + UnusedPrivateClass: + active: true + UnusedPrivateMember: + active: true + allowedNames: '' + UnusedPrivateProperty: + active: true + allowedNames: '_|ignored|expected|serialVersionUID' + excludes: [ '**/build.gradle.kts' ] + UseAnyOrNoneInsteadOfFind: + active: true + UseArrayLiteralsInAnnotations: + active: true + UseCheckNotNull: + active: true + UseCheckOrError: + active: true + UseDataClass: + active: false + allowVars: false + UseEmptyCounterpart: + active: false + UseIfEmptyOrIfBlank: + active: false + UseIfInsteadOfWhen: + active: false + ignoreWhenContainingVariableDeclaration: false + UseIsNullOrEmpty: + active: true + UseLet: + active: false + UseOrEmpty: + active: true + UseRequire: + active: true + UseRequireNotNull: + active: true + UseSumOfInsteadOfFlatMapSize: + active: false + UselessCallOnNotNull: + active: true + UtilityClassWithPublicConstructor: + active: true + VarCouldBeVal: + active: true + ignoreLateinitVar: false + WildcardImport: + active: true + excludeImports: + - 'java.util.*' diff --git a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/dao/DeviceDatabase.kt b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt similarity index 57% rename from shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/dao/DeviceDatabase.kt rename to shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt index 628c09f..dc41845 100644 --- a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/dao/DeviceDatabase.kt +++ b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt @@ -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", ) - } -} \ No newline at end of file +} diff --git a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/NetworkSSL.kt b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/NetworkSSL.kt index da2edfb..901ab2c 100644 --- a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/NetworkSSL.kt +++ b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/rest/NetworkSSL.kt @@ -8,16 +8,20 @@ class NaiveTrustManager : X509TrustManager { override fun checkClientTrusted( chain: Array?, authType: String?, - ) {} + ) { + // Nothing + } override fun checkServerTrusted( chain: Array?, authType: String?, - ) {} + ) { + // Nothing + } override fun getAcceptedIssuers(): Array = arrayOf() } -actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) { +actual fun setupInsecureHttpEngine(config: CIOEngineConfig) { config.https.trustManager = NaiveTrustManager() } diff --git a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt index e5664fb..be6f8aa 100644 --- a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt +++ b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt @@ -1,9 +1,9 @@ package bou.amine.apps.readerforselfossv2.utils import android.text.format.DateUtils -import io.github.aakira.napier.Napier -import kotlinx.datetime.* +import kotlinx.datetime.Clock +@Suppress("detekt:UtilityClassWithPublicConstructor") actual class DateUtils { actual companion object { actual fun parseRelativeDate(dateString: String): String { diff --git a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ModelConverters.kt b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ModelConverters.kt index e5100cf..9ad4fec 100644 --- a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ModelConverters.kt +++ b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ModelConverters.kt @@ -4,46 +4,36 @@ 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) + +val IMAGE_EXTENSION_REGEXP = """\.(jpg|jpeg|png|webp)""".toRegex() actual fun SelfossModel.Item.getImages(): ArrayList { val allImages = ArrayList() for (image in Jsoup.parse(content).getElementsByTag("img")) { val url = image.attr("src") - if (url.lowercase(Locale.US).contains(".jpg") || - url.lowercase(Locale.US).contains(".jpeg") || - url.lowercase(Locale.US).contains(".png") || - url.lowercase(Locale.US).contains(".webp") - ) { + if (IMAGE_EXTENSION_REGEXP.containsMatchIn(url.lowercase(Locale.US))) { allImages.add(url) } } 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 +41,3 @@ actual fun constructUrl( baseUriBuilder.toString() } -} diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/dao/DeviceDatabase.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt similarity index 98% rename from shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/dao/DeviceDatabase.kt rename to shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt index ab41017..bd9338f 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/dao/DeviceDatabase.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt @@ -4,4 +4,4 @@ import app.cash.sqldelight.db.SqlDriver expect class DriverFactory { fun createDriver(): SqlDriver -} \ No newline at end of file +} diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/DI/modules.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/di/Modules.kt similarity index 89% rename from shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/DI/modules.kt rename to shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/di/Modules.kt index f099de4..2429dda 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/DI/modules.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/di/Modules.kt @@ -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 diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/MercuryModel.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/MercuryModel.kt index 05c7666..bd854b0 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/MercuryModel.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/MercuryModel.kt @@ -1,13 +1,16 @@ +@file:Suppress("detekt:LongParameterList") + package bou.amine.apps.readerforselfossv2.model import kotlinx.serialization.Serializable class MercuryModel { + @Suppress("detekt:ConstructorParameterNaming") @Serializable class ParsedContent( val title: String? = null, val content: String? = null, - val lead_image_url: String? = null, // NOSONAR + val lead_image_url: String? = null, val url: String? = null, val error: Boolean? = null, val message: String? = null, diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/ResultModel.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/ResultModel.kt index 48578b4..cabe0b3 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/ResultModel.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/ResultModel.kt @@ -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(val success: Boolean, val data: T? = null) { +class StatusAndData( + val success: Boolean, + val data: T? = null, +) { companion object { - fun succes(d: T): StatusAndData { - return StatusAndData(true, d) - } + fun succes(d: T): StatusAndData = StatusAndData(true, d) - fun error(): StatusAndData { - return StatusAndData(false) - } + fun error(): StatusAndData = StatusAndData(false) } } diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/SelfossModel.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/SelfossModel.kt index 9b2bd99..8f1f74c 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/SelfossModel.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/model/SelfossModel.kt @@ -1,3 +1,5 @@ +@file:Suppress("detekt:LongParameterList") + package bou.amine.apps.readerforselfossv2.model import bou.amine.apps.readerforselfossv2.utils.DateUtils @@ -18,6 +20,10 @@ import kotlinx.serialization.json.booleanOrNull import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonPrimitive +class ModelException( + message: String, +) : Throwable(message) + class SelfossModel { @Serializable data class Tag( @@ -141,7 +147,7 @@ class SelfossModel { } if (stringUrl.isEmptyOrNullOrNullString()) { - throw Exception("Link ${link} was translated to ${stringUrl}, but was empty. Handle this.") + throw ModelException("Link $link was translated to $stringUrl, but was empty. Handle this.") } return stringUrl @@ -170,14 +176,13 @@ class SelfossModel { } } - // TODO: this seems to be super slow. + // this seems to be super slow. object TagsListSerializer : KSerializer> { - override fun deserialize(decoder: Decoder): List { - return when (val json = ((decoder as JsonDecoder).decodeJsonElement())) { + override fun deserialize(decoder: Decoder): List = + 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 +193,7 @@ class SelfossModel { ) { encoder.encodeCollection( PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING), - value.size + value.size, ) { this.toString() } } } @@ -204,10 +209,11 @@ class SelfossModel { } override val descriptor: SerialDescriptor - get() = PrimitiveSerialDescriptor( - "BooleanOrIntForSomeSelfossVersions", - PrimitiveKind.BOOLEAN - ) + get() = + PrimitiveSerialDescriptor( + "BooleanOrIntForSomeSelfossVersions", + PrimitiveKind.BOOLEAN, + ) override fun serialize( encoder: Encoder, @@ -216,4 +222,4 @@ class SelfossModel { TODO("Not yet implemented") } } -} \ No newline at end of file +} diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/Repository.kt similarity index 91% rename from shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt rename to shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/Repository.kt index f84df1f..b842903 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/Repository.kt @@ -1,12 +1,23 @@ +@file:Suppress("detekt:TooManyFunctions") + 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 @@ -14,6 +25,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +private const val MAX_ITEMS_NUMBER = 200 + class Repository( private val api: SelfossApi, private val appSettingsService: AppSettingsService, @@ -118,7 +131,7 @@ class Repository( null, null, null, - 200, + MAX_ITEMS_NUMBER, ) return if (items.success && items.data != null) { items.data @@ -130,6 +143,7 @@ class Repository( } } + @Suppress("detekt:ForbiddenComment") suspend fun reloadBadges(): Boolean { var success = false if (isNetworkAvailable()) { @@ -170,8 +184,8 @@ class Repository( } } - suspend fun getSpouts(): Map { - return if (isNetworkAvailable()) { + suspend fun getSpouts(): Map = + if (isNetworkAvailable()) { val spouts = api.spouts() if (spouts.success && spouts.data != null) { spouts.data @@ -181,7 +195,6 @@ class Repository( } else { throw NetworkUnavailableException() } - } suspend fun getSourcesDetailsOrStats(): ArrayList { var sources = ArrayList() @@ -234,14 +247,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 +264,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 +281,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 +298,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): Boolean { var success = false @@ -361,12 +370,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 +417,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 +431,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 +445,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 +457,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 { @@ -555,6 +564,7 @@ class Repository( item.id.toString(), ) + @Suppress("detekt:SwallowedException") suspend fun tryToCacheItemsAndGetNewOnes(): List { try { val newItems = getMaxItemsForBackground(ItemType.UNREAD) @@ -578,16 +588,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 +631,7 @@ class Repository( _readerItems = readerItems } - fun getReaderItems(): ArrayList { - return _readerItems - } + fun getReaderItems(): ArrayList = _readerItems fun migrate(driverFactory: DriverFactory) { ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1) @@ -634,7 +645,5 @@ class Repository( _selectedSource = null } - fun getSelectedSource(): SelfossModel.SourceDetail? { - return _selectedSource - } + fun getSelectedSource(): SelfossModel.SourceDetail? = _selectedSource } diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/MercuryApi.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/MercuryApi.kt index 5fc678c..35d9eab 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/MercuryApi.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/MercuryApi.kt @@ -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 = bodyOrFailure( @@ -48,4 +47,4 @@ class MercuryApi { parameter("link", url) }, ) -} \ No newline at end of file +} diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/RestUtils.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/RestUtils.kt index f499cdc..d072a63 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/RestUtils.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/RestUtils.kt @@ -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,8 +32,8 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse { } SuccessResponse(false) } -} +@Suppress("detekt:SwallowedException") suspend inline fun bodyOrFailure(r: HttpResponse?): StatusAndData { try { return if (r != null && r.status.isSuccess()) { @@ -98,4 +103,4 @@ suspend fun HttpClient.tryToSubmitForm( url(url) block() } - } \ No newline at end of file + } diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt index fc7bb82..9bab495 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/rest/SelfossApi.kt @@ -1,3 +1,5 @@ +@file:Suppress("detekt:TooManyFunctions", "detekt:LongParameterList", "detekt:LargeClass") + package bou.amine.apps.readerforselfossv2.rest import bou.amine.apps.readerforselfossv2.model.SelfossModel @@ -33,16 +35,20 @@ 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) { +private const val VERSION_WHERE_POST_LOGIN_SHOULD_WORK = 5 + +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 +111,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 +135,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 +160,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 +180,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { }, ) - private fun shouldHaveNewLogout() = - appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0 + private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= VERSION_WHERE_POST_LOGIN_SHOULD_WORK // We are missing 4.1.0 suspend fun logout(): SuccessResponse = if (shouldHaveNewLogout()) { @@ -181,8 +192,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 +215,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 +257,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 +286,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 +313,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 +340,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 +367,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 +394,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 +421,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 +448,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 +471,10 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { suspend fun apiInformation(): StatusAndData = bodyOrFailure( client.tryToGet(url("/api/about")) { - if (appSettingsService.getBasicUserName() - .isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty() + if (appSettingsService + .getBasicUserName() + .isNotEmpty() && + appSettingsService.getBasicPassword().isNotEmpty() ) { headers { append( @@ -465,8 +498,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 +525,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 +552,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 +579,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 +604,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 +657,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 +714,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 +752,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 +771,4 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { } }, ) -} \ No newline at end of file +} diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ACRASettings.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ACRASettings.kt index 07da94d..e3c9644 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ACRASettings.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/ACRASettings.kt @@ -1,3 +1,5 @@ +@file:Suppress("detekt:TooManyFunctions") + package bou.amine.apps.readerforselfossv2.service import com.russhwolf.settings.Settings @@ -121,4 +123,4 @@ class ACRASettings : Settings { longs.remove(key) strings.remove(key) } -} \ No newline at end of file +} diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/AppSettingsService.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/AppSettingsService.kt index 46c2475..505ec74 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/AppSettingsService.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/service/AppSettingsService.kt @@ -1,8 +1,22 @@ +@file:Suppress("detekt:TooManyFunctions") + package bou.amine.apps.readerforselfossv2.service import com.russhwolf.settings.Settings -class AppSettingsService(acraSenderServiceProcess: Boolean = false) { +private const val DEFAULT_FONT_SIZE = 16 + +private const val DEFAULT_REFRESH_MINUTES = 360L + +private const val MIN_REFRESH_MINUTES = 15L + +private const val DEFAULT_API_TIMEOUT = 60L + +private const val DEFAULT_ITEMS_NUMBER = 20 + +class AppSettingsService( + acraSenderServiceProcess: Boolean = false, +) { val settings: Settings = if (acraSenderServiceProcess) { ACRASettings() @@ -11,37 +25,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 = DEFAULT_REFRESH_MINUTES + 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 +63,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 +76,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 +92,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,312 +108,315 @@ 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!! } + @Suppress("detekt:SwallowedException") private fun refreshItemsNumber() { - _itemsNumber = + itemsNumber = try { - settings.getString(API_ITEMS_NUMBER, "20").toInt() + settings.getString(API_ITEMS_NUMBER, DEFAULT_ITEMS_NUMBER.toString()).toInt() } catch (e: Exception) { settings.remove(API_ITEMS_NUMBER) - 20 + DEFAULT_ITEMS_NUMBER } } fun getApiTimeout(): Long { - if (_apiTimeout == null) { + if (apiTimeout == null) { refreshApiTimeout() } - return _apiTimeout!! + return apiTimeout!! } + @Suppress("detekt:MagicNumber") private fun secToMs(n: Long) = n * 1000 + @Suppress("detekt:SwallowedException") private fun refreshApiTimeout() { - _apiTimeout = + apiTimeout = secToMs( try { - val settingsTimeout = settings.getString(API_TIMEOUT, "60") + val settingsTimeout = settings.getString(API_TIMEOUT, DEFAULT_API_TIMEOUT.toString()) if (settingsTimeout.toLong() > 0) { settingsTimeout.toLong() } else { settings.remove(API_TIMEOUT) - 60 + DEFAULT_API_TIMEOUT } } catch (e: Exception) { settings.remove(API_TIMEOUT) - 60 + DEFAULT_API_TIMEOUT }, ) } 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, DEFAULT_REFRESH_MINUTES.toString()).toLong() + if (refreshMinutes <= MIN_REFRESH_MINUTES) { + refreshMinutes = MIN_REFRESH_MINUTES } } fun getRefreshMinutes(): Long { - if (_refreshMinutes != 360L) { + if (refreshMinutes != DEFAULT_REFRESH_MINUTES) { 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 ?: DEFAULT_FONT_SIZE } 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 +495,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 diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt index 7cbcbbd..3678b0d 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt @@ -4,6 +4,12 @@ import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant +class DateParseException( + message: String, + e: Throwable? = null, +) : Throwable(message, e) + +@Suppress("detekt:ThrowsCount") fun String.toParsedDate(): Long { // Possible formats are // yyyy-mm-dd hh:mm:ss format @@ -17,17 +23,22 @@ 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 DateParseException("Couldn't parse $this") } else { - throw Exception("Unrecognized format for $this") + throw DateParseException("Unrecognized format for $this") } } catch (e: Exception) { - throw Exception("parseDate failed for $this", e) + throw DateParseException("parseDate failed for $this", e) } return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds() } +@Suppress("detekt:UtilityClassWithPublicConstructor") expect class DateUtils() { companion object { fun parseRelativeDate(dateString: String): String diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/EntityUtils.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/EntityUtils.kt index 60efc42..816e886 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/EntityUtils.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/EntityUtils.kt @@ -17,7 +17,7 @@ fun SOURCE.toView(): SelfossModel.SourceDetail = this.id.toInt(), this.title, null, - this.tags?.split(","), + this.tags.split(","), this.spout, this.error, this.icon, @@ -74,6 +74,7 @@ fun SelfossModel.Item.toEntity(): ITEM = this.author, ) +@Suppress("detekt:MagicNumber") fun SelfossModel.Tag.getColorHexCode(): String = if (this.color.length == 4) { // #000 val char1 = this.color.get(1) @@ -82,4 +83,4 @@ fun SelfossModel.Tag.getColorHexCode(): String = "#$char1$char1$char2$char2$char3$char3" } else { this.color - } \ No newline at end of file + } diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/Enums.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ItemType.kt similarity index 69% rename from shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/Enums.kt rename to shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ItemType.kt index e5a0ff4..a61b2e0 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/Enums.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/ItemType.kt @@ -1,6 +1,10 @@ package bou.amine.apps.readerforselfossv2.utils -enum class ItemType(val position: Int, val type: String) { +@Suppress("detekt:MagicNumber") +enum class ItemType( + val position: Int, + val type: String, +) { UNREAD(1, "unread"), ALL(2, "all"), STARRED(3, "starred"), diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/StringUtils.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/StringUtils.kt index 7c9f0ca..5994183 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/StringUtils.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/StringUtils.kt @@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.utils fun String?.isEmptyOrNullOrNullString(): Boolean = this == null || this == "null" || this.isEmpty() +@Suppress("detekt:MagicNumber") fun String.longHash(): Long { var h = 98764321261L val l = this.length diff --git a/shared/src/commonTest/kotlin/DatesTest.kt b/shared/src/commonTest/kotlin/bou/amine/apps/readerforselfossv2/repository/DatesTest.kt similarity index 61% rename from shared/src/commonTest/kotlin/DatesTest.kt rename to shared/src/commonTest/kotlin/bou/amine/apps/readerforselfossv2/repository/DatesTest.kt index e074d04..81b199e 100644 --- a/shared/src/commonTest/kotlin/DatesTest.kt +++ b/shared/src/commonTest/kotlin/bou/amine/apps/readerforselfossv2/repository/DatesTest.kt @@ -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) diff --git a/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt b/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt index 17e359d..cb47fda 100644 --- a/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt +++ b/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt @@ -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") - } -} \ No newline at end of file + actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db") +} diff --git a/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt b/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt new file mode 100644 index 0000000..1dae9ff --- /dev/null +++ b/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt @@ -0,0 +1,7 @@ +package bou.amine.apps.readerforselfossv2.rest + +import io.ktor.client.engine.cio.CIOEngineConfig + +@Suppress("detekt:EmptyFunctionBlock") +actual fun setupInsecureHttpEngine(config: CIOEngineConfig) { +} diff --git a/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/setupInsecureHTTPEngine.kt b/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/setupInsecureHTTPEngine.kt deleted file mode 100644 index 0bfd800..0000000 --- a/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/setupInsecureHTTPEngine.kt +++ /dev/null @@ -1,6 +0,0 @@ -package bou.amine.apps.readerforselfossv2.rest - -import io.ktor.client.engine.cio.CIOEngineConfig - -actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) { -} diff --git a/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt b/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt index 6b6cb5b..d0e7996 100644 --- a/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt +++ b/shared/src/iosArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt @@ -1,9 +1,10 @@ package bou.amine.apps.readerforselfossv2.utils +@Suppress("detekt:UtilityClassWithPublicConstructor") actual class DateUtils { actual companion object { actual fun parseRelativeDate(dateString: String): String { TODO("Not yet implemented") } } -} \ No newline at end of file +} diff --git a/shared/src/iosSimulatorArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.iosSimulatorArm64.kt b/shared/src/iosSimulatorArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt similarity index 81% rename from shared/src/iosSimulatorArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.iosSimulatorArm64.kt rename to shared/src/iosSimulatorArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt index ee78316..3eb0698 100644 --- a/shared/src/iosSimulatorArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.iosSimulatorArm64.kt +++ b/shared/src/iosSimulatorArm64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt @@ -1,9 +1,10 @@ package bou.amine.apps.readerforselfossv2.utils +@Suppress("detekt:UtilityClassWithPublicConstructor") actual class DateUtils actual constructor() { actual companion object { actual fun parseRelativeDate(dateString: String): String { TODO("Not yet implemented") } } -} \ No newline at end of file +} diff --git a/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt b/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt index 17e359d..cb47fda 100644 --- a/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt +++ b/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/dao/DriverFactory.kt @@ -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") - } -} \ No newline at end of file + actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db") +} diff --git a/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt b/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt new file mode 100644 index 0000000..1dae9ff --- /dev/null +++ b/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/SetupInsecureHttpEngine.kt @@ -0,0 +1,7 @@ +package bou.amine.apps.readerforselfossv2.rest + +import io.ktor.client.engine.cio.CIOEngineConfig + +@Suppress("detekt:EmptyFunctionBlock") +actual fun setupInsecureHttpEngine(config: CIOEngineConfig) { +} diff --git a/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/setupInsecureHTTPEngine.kt b/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/setupInsecureHTTPEngine.kt deleted file mode 100644 index 0bfd800..0000000 --- a/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/rest/setupInsecureHTTPEngine.kt +++ /dev/null @@ -1,6 +0,0 @@ -package bou.amine.apps.readerforselfossv2.rest - -import io.ktor.client.engine.cio.CIOEngineConfig - -actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) { -} diff --git a/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt b/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt index 6b6cb5b..d0e7996 100644 --- a/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt +++ b/shared/src/iosX64Main/kotlin/bou/amine/apps/readerforselfossv2/utils/DateUtils.kt @@ -1,9 +1,10 @@ package bou.amine.apps.readerforselfossv2.utils +@Suppress("detekt:UtilityClassWithPublicConstructor") actual class DateUtils { actual companion object { actual fun parseRelativeDate(dateString: String): String { TODO("Not yet implemented") } } -} \ No newline at end of file +}