From 4388615a59ffe8bd9ea8d57479b83a94acf20d56 Mon Sep 17 00:00:00 2001 From: Shinokuni Date: Tue, 2 Jul 2024 18:18:39 +0200 Subject: [PATCH] Remove old app module Time to say goodbye, see you old friend --- app/.gitignore | 1 - app/build.gradle | 118 --- app/proguard-rules.pro | 51 - .../readrops/app/SyncResultAnalyserTest.kt | 306 ------ app/src/debug/AndroidManifest.xml | 18 - .../com/readrops/app/ReadropsDebugApp.java | 55 -- app/src/debug/res/values/strings.xml | 3 - app/src/main/AndroidManifest.xml | 90 -- .../main/java/com/readrops/app/AppModule.kt | 75 -- .../main/java/com/readrops/app/ReadropsApp.kt | 71 -- .../app/account/AccountTypeListActivity.java | 196 ---- .../app/account/AccountTypeListAdapter.java | 66 -- .../app/account/AccountViewModel.java | 71 -- .../app/account/AddAccountActivity.java | 239 ----- .../app/addfeed/AccountArrayAdapter.java | 51 - .../readrops/app/addfeed/AddFeedActivity.java | 333 ------- .../app/addfeed/AddFeedsViewModel.java | 56 -- .../app/addfeed/FeedInsertionResult.java | 138 --- .../readrops/app/addfeed/ParsingResult.java | 157 ---- .../ManageFeedsFoldersActivity.java | 156 ---- .../ManageFeedsFoldersViewModel.java | 88 -- .../feeds/EditFeedDialogFragment.java | 138 --- .../feeds/FeedOptionsDialogFragment.kt | 87 -- .../app/feedsfolders/feeds/FeedsAdapter.java | 130 --- .../app/feedsfolders/feeds/FeedsFragment.java | 153 --- .../folders/FolderOptionsDialogFragment.kt | 66 -- .../feedsfolders/folders/FoldersAdapter.java | 106 --- .../feedsfolders/folders/FoldersFragment.java | 174 ---- .../com/readrops/app/item/ItemActivity.java | 429 --------- .../com/readrops/app/item/ItemViewModel.java | 69 -- .../com/readrops/app/item/WebViewActivity.kt | 133 --- .../readrops/app/itemslist/DrawerManager.java | 412 --------- .../readrops/app/itemslist/MainActivity.java | 868 ------------------ .../app/itemslist/MainItemListAdapter.java | 372 -------- .../readrops/app/itemslist/MainViewModel.java | 266 ------ .../NotificationPermissionActivity.kt | 150 --- .../NotificationPermissionListAdapter.kt | 67 -- .../NotificationPermissionViewModel.kt | 27 - .../notifications/sync/SyncResultAnalyser.kt | 122 --- .../notifications/sync/SyncResultDebugData.kt | 100 -- .../sync/SyncResultNotifContent.kt | 12 - .../app/notifications/sync/SyncWorker.kt | 211 ----- .../app/repositories/ARepository.java | 207 ----- .../readrops/app/repositories/FeedUpdate.kt | 9 - .../app/repositories/FreshRSSRepository.java | 301 ------ .../app/repositories/LocalFeedRepository.java | 188 ---- .../app/repositories/NextNewsRepository.java | 352 ------- .../app/settings/AccountSettingsFragment.java | 325 ------- .../app/settings/SettingsActivity.java | 61 -- .../app/settings/SettingsFragment.java | 135 --- .../java/com/readrops/app/utils/FileUtils.kt | 69 -- .../com/readrops/app/utils/HtmlParser.java | 110 --- .../java/com/readrops/app/utils/OPMLHelper.kt | 27 - .../readrops/app/utils/PermissionManager.kt | 24 - .../readrops/app/utils/ReadropsGlideModule.kt | 23 - .../com/readrops/app/utils/ReadropsKeys.kt | 25 - .../app/utils/SharedPreferencesManager.java | 86 -- .../java/com/readrops/app/utils/Utils.java | 124 --- .../CustomExpandableBadgeDrawerItem.java | 174 ---- .../app/utils/customviews/EmptyListView.kt | 24 - .../customviews/ReadropsItemTouchCallback.kt | 141 --- .../utils/customviews/ReadropsWebView.java | 118 --- .../app/utils/feedscolors/FeedColors.kt | 38 - .../feedscolors/FeedsColorsIntentService.kt | 48 - .../color/generic_button_color_selector.xml | 6 - .../res/drawable-v23/splash_background.xml | 11 - .../main/res/drawable/header_background.png | Bin 154749 -> 0 bytes app/src/main/res/drawable/ic_about_grey.xml | 5 - app/src/main/res/drawable/ic_account.xml | 5 - .../main/res/drawable/ic_add_account_grey.xml | 5 - app/src/main/res/drawable/ic_add_white.xml | 5 - app/src/main/res/drawable/ic_cancel_grey.xml | 5 - app/src/main/res/drawable/ic_check_green.xml | 5 - app/src/main/res/drawable/ic_delete.xml | 9 - app/src/main/res/drawable/ic_delete_grey.xml | 5 - app/src/main/res/drawable/ic_edit.xml | 9 - app/src/main/res/drawable/ic_edit_grey.xml | 5 - app/src/main/res/drawable/ic_empty_star.xml | 5 - app/src/main/res/drawable/ic_error.xml | 5 - app/src/main/res/drawable/ic_filter.xml | 5 - app/src/main/res/drawable/ic_folder_grey.xml | 5 - .../main/res/drawable/ic_import_export.xml | 5 - app/src/main/res/drawable/ic_new_folder.xml | 5 - app/src/main/res/drawable/ic_notif.xml | 20 - .../main/res/drawable/ic_notifications.xml | 5 - .../main/res/drawable/ic_open_in_browser.xml | 9 - .../res/drawable/ic_open_in_browser_white.xml | 5 - app/src/main/res/drawable/ic_read.xml | 5 - app/src/main/res/drawable/ic_read_later.xml | 5 - app/src/main/res/drawable/ic_reading_time.xml | 24 - app/src/main/res/drawable/ic_refresh.xml | 5 - .../main/res/drawable/ic_rss_feed_grey.xml | 6 - .../main/res/drawable/ic_select_all_white.xml | 5 - app/src/main/res/drawable/ic_settings.xml | 9 - app/src/main/res/drawable/ic_share_white.xml | 5 - app/src/main/res/drawable/ic_star.xml | 5 - app/src/main/res/drawable/ic_sync.xml | 5 - app/src/main/res/drawable/ic_timeline.xml | 5 - app/src/main/res/drawable/ic_unread.xml | 5 - app/src/main/res/drawable/ic_warning_red.xml | 5 - .../res/drawable/item_date_background.xml | 6 - app/src/main/res/drawable/logo.png | Bin 7915 -> 0 bytes .../main/res/drawable/splash_background.xml | 5 - app/src/main/res/drawable/tab_indicator.xml | 5 - app/src/main/res/drawable/toolbar_scrim.xml | 8 - app/src/main/res/layout/account_type_item.xml | 28 - .../res/layout/activity_account_type_list.xml | 79 -- .../main/res/layout/activity_add_account.xml | 132 --- app/src/main/res/layout/activity_add_feed.xml | 165 ---- app/src/main/res/layout/activity_item.xml | 179 ---- app/src/main/res/layout/activity_main.xml | 108 --- .../layout/activity_manage_feeds_folders.xml | 40 - .../activity_notification_permission.xml | 84 -- app/src/main/res/layout/activity_settings.xml | 17 - app/src/main/res/layout/activity_web_view.xml | 56 -- app/src/main/res/layout/add_feed_item.xml | 46 - .../layout/custom_expandable_drawer_item.xml | 109 --- app/src/main/res/layout/edit_feed_layout.xml | 64 -- app/src/main/res/layout/empty_list_view.xml | 25 - .../main/res/layout/feed_insertion_result.xml | 27 - app/src/main/res/layout/feed_layout.xml | 89 -- .../main/res/layout/feed_options_layout.xml | 93 -- app/src/main/res/layout/folder_layout.xml | 53 -- .../main/res/layout/folder_options_layout.xml | 68 -- app/src/main/res/layout/fragment_feeds.xml | 31 - app/src/main/res/layout/fragment_folders.xml | 31 - app/src/main/res/layout/list_item.xml | 151 --- .../layout/notification_permission_layout.xml | 51 - app/src/main/res/menu/feeds_menu.xml | 11 - .../res/menu/item_list_contextual_menu.xml | 23 - app/src/main/res/menu/item_list_menu.xml | 23 - app/src/main/res/menu/item_menu.xml | 20 - app/src/main/res/menu/webview_menu.xml | 17 - app/src/main/res/values-de/strings.xml | 138 --- app/src/main/res/values-fr/strings.xml | 144 --- app/src/main/res/values-in/strings.xml | 137 --- app/src/main/res/values-it/strings.xml | 138 --- app/src/main/res/values-nb-rNO/strings.xml | 69 -- app/src/main/res/values-night/styles.xml | 23 - app/src/main/res/values/arrays.xml | 79 -- app/src/main/res/values/attrs.xml | 14 - app/src/main/res/values/colors.xml | 9 - app/src/main/res/values/html.xml | 73 -- app/src/main/res/values/strings.xml | 149 --- app/src/main/res/values/styles.xml | 41 - app/src/main/res/xml/acount_preferences.xml | 29 - app/src/main/res/xml/file_paths.xml | 8 - .../main/res/xml/network_security_config.xml | 13 - app/src/main/res/xml/preferences.xml | 52 -- .../java/com/readrops/app/HtmlParserTest.kt | 43 - .../test/java/com/readrops/app/UtilsTest.java | 22 - settings.gradle | 2 +- 152 files changed, 1 insertion(+), 12299 deletions(-) delete mode 100644 app/.gitignore delete mode 100644 app/build.gradle delete mode 100644 app/proguard-rules.pro delete mode 100644 app/src/androidTest/java/com/readrops/app/SyncResultAnalyserTest.kt delete mode 100644 app/src/debug/AndroidManifest.xml delete mode 100644 app/src/debug/java/com/readrops/app/ReadropsDebugApp.java delete mode 100644 app/src/debug/res/values/strings.xml delete mode 100644 app/src/main/AndroidManifest.xml delete mode 100644 app/src/main/java/com/readrops/app/AppModule.kt delete mode 100644 app/src/main/java/com/readrops/app/ReadropsApp.kt delete mode 100644 app/src/main/java/com/readrops/app/account/AccountTypeListActivity.java delete mode 100644 app/src/main/java/com/readrops/app/account/AccountTypeListAdapter.java delete mode 100644 app/src/main/java/com/readrops/app/account/AccountViewModel.java delete mode 100644 app/src/main/java/com/readrops/app/account/AddAccountActivity.java delete mode 100644 app/src/main/java/com/readrops/app/addfeed/AccountArrayAdapter.java delete mode 100644 app/src/main/java/com/readrops/app/addfeed/AddFeedActivity.java delete mode 100644 app/src/main/java/com/readrops/app/addfeed/AddFeedsViewModel.java delete mode 100644 app/src/main/java/com/readrops/app/addfeed/FeedInsertionResult.java delete mode 100644 app/src/main/java/com/readrops/app/addfeed/ParsingResult.java delete mode 100644 app/src/main/java/com/readrops/app/feedsfolders/ManageFeedsFoldersActivity.java delete mode 100644 app/src/main/java/com/readrops/app/feedsfolders/ManageFeedsFoldersViewModel.java delete mode 100644 app/src/main/java/com/readrops/app/feedsfolders/feeds/EditFeedDialogFragment.java delete mode 100644 app/src/main/java/com/readrops/app/feedsfolders/feeds/FeedOptionsDialogFragment.kt delete mode 100644 app/src/main/java/com/readrops/app/feedsfolders/feeds/FeedsAdapter.java delete mode 100644 app/src/main/java/com/readrops/app/feedsfolders/feeds/FeedsFragment.java delete mode 100644 app/src/main/java/com/readrops/app/feedsfolders/folders/FolderOptionsDialogFragment.kt delete mode 100644 app/src/main/java/com/readrops/app/feedsfolders/folders/FoldersAdapter.java delete mode 100644 app/src/main/java/com/readrops/app/feedsfolders/folders/FoldersFragment.java delete mode 100644 app/src/main/java/com/readrops/app/item/ItemActivity.java delete mode 100644 app/src/main/java/com/readrops/app/item/ItemViewModel.java delete mode 100644 app/src/main/java/com/readrops/app/item/WebViewActivity.kt delete mode 100644 app/src/main/java/com/readrops/app/itemslist/DrawerManager.java delete mode 100644 app/src/main/java/com/readrops/app/itemslist/MainActivity.java delete mode 100644 app/src/main/java/com/readrops/app/itemslist/MainItemListAdapter.java delete mode 100644 app/src/main/java/com/readrops/app/itemslist/MainViewModel.java delete mode 100644 app/src/main/java/com/readrops/app/notifications/NotificationPermissionActivity.kt delete mode 100644 app/src/main/java/com/readrops/app/notifications/NotificationPermissionListAdapter.kt delete mode 100644 app/src/main/java/com/readrops/app/notifications/NotificationPermissionViewModel.kt delete mode 100644 app/src/main/java/com/readrops/app/notifications/sync/SyncResultAnalyser.kt delete mode 100644 app/src/main/java/com/readrops/app/notifications/sync/SyncResultDebugData.kt delete mode 100644 app/src/main/java/com/readrops/app/notifications/sync/SyncResultNotifContent.kt delete mode 100644 app/src/main/java/com/readrops/app/notifications/sync/SyncWorker.kt delete mode 100644 app/src/main/java/com/readrops/app/repositories/ARepository.java delete mode 100644 app/src/main/java/com/readrops/app/repositories/FeedUpdate.kt delete mode 100644 app/src/main/java/com/readrops/app/repositories/FreshRSSRepository.java delete mode 100644 app/src/main/java/com/readrops/app/repositories/LocalFeedRepository.java delete mode 100644 app/src/main/java/com/readrops/app/repositories/NextNewsRepository.java delete mode 100644 app/src/main/java/com/readrops/app/settings/AccountSettingsFragment.java delete mode 100644 app/src/main/java/com/readrops/app/settings/SettingsActivity.java delete mode 100644 app/src/main/java/com/readrops/app/settings/SettingsFragment.java delete mode 100644 app/src/main/java/com/readrops/app/utils/FileUtils.kt delete mode 100644 app/src/main/java/com/readrops/app/utils/HtmlParser.java delete mode 100644 app/src/main/java/com/readrops/app/utils/OPMLHelper.kt delete mode 100644 app/src/main/java/com/readrops/app/utils/PermissionManager.kt delete mode 100644 app/src/main/java/com/readrops/app/utils/ReadropsGlideModule.kt delete mode 100644 app/src/main/java/com/readrops/app/utils/ReadropsKeys.kt delete mode 100644 app/src/main/java/com/readrops/app/utils/SharedPreferencesManager.java delete mode 100644 app/src/main/java/com/readrops/app/utils/Utils.java delete mode 100644 app/src/main/java/com/readrops/app/utils/customviews/CustomExpandableBadgeDrawerItem.java delete mode 100644 app/src/main/java/com/readrops/app/utils/customviews/EmptyListView.kt delete mode 100644 app/src/main/java/com/readrops/app/utils/customviews/ReadropsItemTouchCallback.kt delete mode 100644 app/src/main/java/com/readrops/app/utils/customviews/ReadropsWebView.java delete mode 100644 app/src/main/java/com/readrops/app/utils/feedscolors/FeedColors.kt delete mode 100644 app/src/main/java/com/readrops/app/utils/feedscolors/FeedsColorsIntentService.kt delete mode 100644 app/src/main/res/color/generic_button_color_selector.xml delete mode 100644 app/src/main/res/drawable-v23/splash_background.xml delete mode 100644 app/src/main/res/drawable/header_background.png delete mode 100644 app/src/main/res/drawable/ic_about_grey.xml delete mode 100644 app/src/main/res/drawable/ic_account.xml delete mode 100644 app/src/main/res/drawable/ic_add_account_grey.xml delete mode 100644 app/src/main/res/drawable/ic_add_white.xml delete mode 100644 app/src/main/res/drawable/ic_cancel_grey.xml delete mode 100644 app/src/main/res/drawable/ic_check_green.xml delete mode 100644 app/src/main/res/drawable/ic_delete.xml delete mode 100644 app/src/main/res/drawable/ic_delete_grey.xml delete mode 100644 app/src/main/res/drawable/ic_edit.xml delete mode 100644 app/src/main/res/drawable/ic_edit_grey.xml delete mode 100644 app/src/main/res/drawable/ic_empty_star.xml delete mode 100644 app/src/main/res/drawable/ic_error.xml delete mode 100644 app/src/main/res/drawable/ic_filter.xml delete mode 100644 app/src/main/res/drawable/ic_folder_grey.xml delete mode 100644 app/src/main/res/drawable/ic_import_export.xml delete mode 100644 app/src/main/res/drawable/ic_new_folder.xml delete mode 100644 app/src/main/res/drawable/ic_notif.xml delete mode 100644 app/src/main/res/drawable/ic_notifications.xml delete mode 100644 app/src/main/res/drawable/ic_open_in_browser.xml delete mode 100644 app/src/main/res/drawable/ic_open_in_browser_white.xml delete mode 100644 app/src/main/res/drawable/ic_read.xml delete mode 100644 app/src/main/res/drawable/ic_read_later.xml delete mode 100644 app/src/main/res/drawable/ic_reading_time.xml delete mode 100644 app/src/main/res/drawable/ic_refresh.xml delete mode 100644 app/src/main/res/drawable/ic_rss_feed_grey.xml delete mode 100644 app/src/main/res/drawable/ic_select_all_white.xml delete mode 100644 app/src/main/res/drawable/ic_settings.xml delete mode 100644 app/src/main/res/drawable/ic_share_white.xml delete mode 100644 app/src/main/res/drawable/ic_star.xml delete mode 100644 app/src/main/res/drawable/ic_sync.xml delete mode 100644 app/src/main/res/drawable/ic_timeline.xml delete mode 100644 app/src/main/res/drawable/ic_unread.xml delete mode 100644 app/src/main/res/drawable/ic_warning_red.xml delete mode 100644 app/src/main/res/drawable/item_date_background.xml delete mode 100644 app/src/main/res/drawable/logo.png delete mode 100644 app/src/main/res/drawable/splash_background.xml delete mode 100644 app/src/main/res/drawable/tab_indicator.xml delete mode 100644 app/src/main/res/drawable/toolbar_scrim.xml delete mode 100644 app/src/main/res/layout/account_type_item.xml delete mode 100644 app/src/main/res/layout/activity_account_type_list.xml delete mode 100644 app/src/main/res/layout/activity_add_account.xml delete mode 100644 app/src/main/res/layout/activity_add_feed.xml delete mode 100644 app/src/main/res/layout/activity_item.xml delete mode 100644 app/src/main/res/layout/activity_main.xml delete mode 100644 app/src/main/res/layout/activity_manage_feeds_folders.xml delete mode 100644 app/src/main/res/layout/activity_notification_permission.xml delete mode 100644 app/src/main/res/layout/activity_settings.xml delete mode 100644 app/src/main/res/layout/activity_web_view.xml delete mode 100644 app/src/main/res/layout/add_feed_item.xml delete mode 100644 app/src/main/res/layout/custom_expandable_drawer_item.xml delete mode 100644 app/src/main/res/layout/edit_feed_layout.xml delete mode 100644 app/src/main/res/layout/empty_list_view.xml delete mode 100644 app/src/main/res/layout/feed_insertion_result.xml delete mode 100644 app/src/main/res/layout/feed_layout.xml delete mode 100644 app/src/main/res/layout/feed_options_layout.xml delete mode 100644 app/src/main/res/layout/folder_layout.xml delete mode 100644 app/src/main/res/layout/folder_options_layout.xml delete mode 100644 app/src/main/res/layout/fragment_feeds.xml delete mode 100644 app/src/main/res/layout/fragment_folders.xml delete mode 100644 app/src/main/res/layout/list_item.xml delete mode 100644 app/src/main/res/layout/notification_permission_layout.xml delete mode 100644 app/src/main/res/menu/feeds_menu.xml delete mode 100644 app/src/main/res/menu/item_list_contextual_menu.xml delete mode 100644 app/src/main/res/menu/item_list_menu.xml delete mode 100644 app/src/main/res/menu/item_menu.xml delete mode 100644 app/src/main/res/menu/webview_menu.xml delete mode 100644 app/src/main/res/values-de/strings.xml delete mode 100644 app/src/main/res/values-fr/strings.xml delete mode 100644 app/src/main/res/values-in/strings.xml delete mode 100644 app/src/main/res/values-it/strings.xml delete mode 100644 app/src/main/res/values-nb-rNO/strings.xml delete mode 100644 app/src/main/res/values-night/styles.xml delete mode 100644 app/src/main/res/values/arrays.xml delete mode 100644 app/src/main/res/values/attrs.xml delete mode 100644 app/src/main/res/values/colors.xml delete mode 100644 app/src/main/res/values/html.xml delete mode 100644 app/src/main/res/values/strings.xml delete mode 100644 app/src/main/res/values/styles.xml delete mode 100644 app/src/main/res/xml/acount_preferences.xml delete mode 100644 app/src/main/res/xml/file_paths.xml delete mode 100644 app/src/main/res/xml/network_security_config.xml delete mode 100644 app/src/main/res/xml/preferences.xml delete mode 100644 app/src/test/java/com/readrops/app/HtmlParserTest.kt delete mode 100644 app/src/test/java/com/readrops/app/UtilsTest.java diff --git a/app/.gitignore b/app/.gitignore deleted file mode 100644 index 796b96d1..00000000 --- a/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index e851b826..00000000 --- a/app/build.gradle +++ /dev/null @@ -1,118 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-kapt' - -android { - compileSdkVersion rootProject.ext.compileSdkVersion - - defaultConfig { - applicationId "com.readrops.app" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - buildToolsVersion rootProject.ext.buildToolsVersion - - versionCode 14 - versionName "1.3.1" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - testOptions { - unitTests.returnDefaultValues = true - } - buildTypes { - release { - minifyEnabled true - shrinkResources true - - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - - debug { - minifyEnabled false - shrinkResources false - - testCoverageEnabled true - applicationIdSuffix ".debug" - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - compileOptions { - coreLibraryDesugaringEnabled true - - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = '17' - } - - buildFeatures { - viewBinding true - buildConfig true - compose true - } - - composeOptions { - kotlinCompilerExtensionVersion = "1.4.0" - } - - lint { - abortOnError false - } - - - namespace 'com.readrops.app' -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation project(':api') - implementation project(':db') - - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - - testImplementation 'junit:junit:4.13' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test:runner:1.4.0' - androidTestImplementation 'androidx.test:rules:1.4.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - - implementation 'com.google.android.material:material:1.4.0' - implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.palette:palette-ktx:1.0.0' - implementation 'androidx.recyclerview:recyclerview:1.2.1' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.preference:preference:1.1.1' - implementation "androidx.work:work-runtime-ktx:2.8.0" - implementation "androidx.fragment:fragment-ktx:1.3.5" - implementation "androidx.browser:browser:1.3.0" - - implementation(libs.bundles.koin) - testImplementation(libs.bundles.kointest) - - implementation 'com.github.bumptech.glide:glide:4.12.0' - kapt 'com.github.bumptech.glide:compiler:4.12.0' - implementation 'com.github.bumptech.glide:okhttp3-integration:4.12.0' - implementation('com.github.bumptech.glide:recyclerview-integration:4.12.0') { - transitive = false - } - - implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - kapt 'androidx.lifecycle:lifecycle-common-java8:2.3.1' - - implementation 'com.afollestad.material-dialogs:core:0.9.6.0' - - implementation 'com.mikepenz:fastadapter:3.2.9' - implementation 'com.mikepenz:fastadapter-commons:3.3.0' - implementation 'com.mikepenz:materialdrawer:6.1.2' - implementation "com.mikepenz:aboutlibraries:6.2.3" - implementation "com.mikepenz:iconics-views:3.2.5" - implementation "com.mikepenz:iconics-core:3.2.5" - - debugImplementation 'com.facebook.flipper:flipper:0.96.1' - debugImplementation 'com.facebook.soloader:soloader:0.10.1' - debugImplementation 'com.facebook.flipper:flipper-network-plugin:0.96.1' - - implementation(libs.bundles.room) - implementation(libs.bundles.paging) -} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro deleted file mode 100644 index d755688e..00000000 --- a/app/proguard-rules.pro +++ /dev/null @@ -1,51 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile - --dontwarn org.xmlpull.v1.XmlPullParser --dontwarn org.xmlpull.v1.XmlSerializer --keep class org.xmlpull.v1.* {*;} - --keep class org.simpleframework.xml.** { *; } - --keep class com.readrops.api.services.freshrss.json.** { *; } --keep class com.readrops.api.services.nextcloudnews.json.** { *; } - --keep class com.readrops.api.localfeed.** { *; } - --keep class com.readrops.api.opml.model.** { *; } - -# Please add these rules to your existing keep rules in order to suppress warnings. -# This is generated automatically by the Android Gradle plugin. --dontwarn javax.xml.stream.Location --dontwarn javax.xml.stream.XMLInputFactory --dontwarn javax.xml.stream.XMLStreamReader --dontwarn org.bouncycastle.jsse.BCSSLParameters --dontwarn org.bouncycastle.jsse.BCSSLSocket --dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider --dontwarn org.conscrypt.Conscrypt$Version --dontwarn org.conscrypt.Conscrypt --dontwarn org.conscrypt.ConscryptHostnameVerifier --dontwarn org.joda.convert.FromString --dontwarn org.joda.convert.ToString --dontwarn org.openjsse.javax.net.ssl.SSLParameters --dontwarn org.openjsse.javax.net.ssl.SSLSocket --dontwarn org.openjsse.net.ssl.OpenJSSE \ No newline at end of file diff --git a/app/src/androidTest/java/com/readrops/app/SyncResultAnalyserTest.kt b/app/src/androidTest/java/com/readrops/app/SyncResultAnalyserTest.kt deleted file mode 100644 index 782c724d..00000000 --- a/app/src/androidTest/java/com/readrops/app/SyncResultAnalyserTest.kt +++ /dev/null @@ -1,306 +0,0 @@ -package com.readrops.app - -import android.content.Context -import androidx.room.Room -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry -import com.readrops.app.notifications.sync.SyncResultAnalyser -import com.readrops.db.Database -import com.readrops.db.entities.Feed -import com.readrops.db.entities.Item -import com.readrops.db.entities.account.Account -import com.readrops.db.entities.account.AccountType -import com.readrops.api.services.SyncResult -import org.joda.time.LocalDateTime -import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class SyncResultAnalyserTest { - - private lateinit var database: Database - - private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext - private val account1 = Account().apply { - accountName = "test account 1" - accountType = AccountType.FRESHRSS - isNotificationsEnabled = true - } - - private val account2 = Account().apply { - accountName = "test account 2" - accountType = AccountType.NEXTCLOUD_NEWS - isNotificationsEnabled = false - } - - private val account3 = Account().apply { - accountName = "test account 3" - accountType = AccountType.LOCAL - isNotificationsEnabled = true - } - - @Before - fun setupDb() { - database = Room.inMemoryDatabaseBuilder(context, Database::class.java) - .build() - - var account1Id = 0 - database.accountDao().insert(account1).subscribe { id -> account1Id = id.toInt() } - account1.id = account1Id - - var account2Id = 0 - database.accountDao().insert(account2).subscribe { id -> account2Id = id.toInt() } - account2.id = account2Id - - var account3Id = 0 - database.accountDao().insert(account3).subscribe { id -> account3Id = id.toInt() } - account3.id = account3Id - - val accountIds = listOf(account1Id, account2Id, account3Id) - for (i in 0..2) { - val feed = Feed().apply { - name = "feed ${i + 1}" - iconUrl = "https://i0.wp.com/mrmondialisation.org/wp-content/uploads/2017/05/ico_final.gif" - this.accountId = accountIds.find { it == (i + 1) }!! - isNotificationEnabled = i % 2 == 0 - } - - database.feedDao().insert(feed).subscribe() - } - } - - @After - fun closeDb() { - database.close() - } - - @Test - fun testOneElementEveryWhere() { - val item = Item().apply { - title = "caseOneElementEveryWhere" - feedId = 1 - remoteId = "item 1" - pubDate = LocalDateTime.now() - } - - database.itemDao() - .insert(item) - .subscribe() - - val syncResult = SyncResult().apply { items = mutableListOf(item) } - val notifContent = SyncResultAnalyser(context, mapOf(Pair(account1, syncResult)), database).getSyncNotifContent() - - assertEquals("caseOneElementEveryWhere", notifContent.content) - assertEquals("feed 1", notifContent.title) - assertTrue(notifContent.largeIcon != null) - assertTrue(notifContent.accountId!! > 0) - - database.itemDao() - .delete(item) - .subscribe() - } - - @Test - fun testTwoItemsOneFeed() { - val item = Item().apply { - title = "caseTwoItemsOneFeed" - feedId = 1 - } - - val syncResult = SyncResult().apply { items = listOf(item, item, item) } - val notifContent = SyncResultAnalyser(context, mapOf(Pair(account1, syncResult)), database).getSyncNotifContent() - - assertEquals(context.getString(R.string.new_items, 3), notifContent.content) - assertEquals("feed 1", notifContent.title) - assertTrue(notifContent.largeIcon != null) - assertTrue(notifContent.accountId!! > 0) - } - - @Test - fun testMultipleFeeds() { - val item = Item().apply { feedId = 1 } - val item2 = Item().apply { feedId = 3 } - - val syncResult = SyncResult().apply { items = listOf(item, item2) } - val notifContent = SyncResultAnalyser(context, mapOf(Pair(account1, syncResult)), database).getSyncNotifContent() - - assertEquals(context.getString(R.string.new_items, 2), notifContent.content) - assertEquals(account1.accountName, notifContent.title) - assertTrue(notifContent.largeIcon != null) - assertTrue(notifContent.accountId!! > 0) - } - - @Test - fun testMultipleAccounts() { - val item = Item().apply { feedId = 1 } - val item2 = Item().apply { feedId = 3 } - - val syncResult = SyncResult().apply { items = listOf(item, item2) } - val syncResult2 = SyncResult().apply { items = listOf(item, item2) } - - val syncResults = mutableMapOf().apply { - put(account1, syncResult) - put(account3, syncResult2) - } - - val notifContent = SyncResultAnalyser(context, syncResults, database).getSyncNotifContent() - - assertEquals(context.getString(R.string.new_items, 4), notifContent.title) - } - - @Test - fun testAccountNotificationsDisabled() { - val item1 = Item().apply { - title = "testAccountNotificationsDisabled" - feedId = 1 - } - - val item2 = Item().apply { - title = "testAccountNotificationsDisabled2" - feedId = 1 - } - - val syncResult = SyncResult().apply { items = listOf(item1, item2) } - val notifContent = SyncResultAnalyser(context, mapOf(Pair(account2, syncResult)), database).getSyncNotifContent() - - assert(notifContent.title == null) - assert(notifContent.content == null) - assert(notifContent.largeIcon == null) - } - - @Test - fun testFeedNotificationsDisabled() { - val item1 = Item().apply { - title = "testAccountNotificationsDisabled" - feedId = 2 - } - - val item2 = Item().apply { - title = "testAccountNotificationsDisabled2" - feedId = 2 - } - - val syncResult = SyncResult().apply { items = listOf(item1, item2) } - val notifContent = SyncResultAnalyser(context, mapOf(Pair(account1, syncResult)), database).getSyncNotifContent() - - assert(notifContent.title == null) - assert(notifContent.content == null) - assert(notifContent.largeIcon == null) - } - - @Test - fun testTwoAccountsWithOneAccountNotificationsEnabled() { - val item1 = Item().apply { - title = "testTwoAccountsWithOneAccountNotificationsEnabled" - feedId = 1 - remoteId = "remoteId 1" - pubDate = LocalDateTime.now() - } - - val item2 = Item().apply { - title = "testTwoAccountsWithOneAccountNotificationsEnabled2" - feedId = 3 - } - - val item3 = Item().apply { - title = "testTwoAccountsWithOneAccountNotificationsEnabled3" - feedId = 3 - } - - database.itemDao().insert(item1).subscribe() - - val syncResult1 = SyncResult().apply { items = listOf(item1) } - val syncResult2 = SyncResult().apply { items = listOf(item2, item3) } - - val syncResults = mutableMapOf().apply { - put(account1, syncResult1) - put(account2, syncResult2) - } - - val notifContent = SyncResultAnalyser(context, syncResults, database).getSyncNotifContent() - - assertEquals("testTwoAccountsWithOneAccountNotificationsEnabled", notifContent.content) - assertEquals("feed 1", notifContent.title) - assertTrue(notifContent.largeIcon != null) - assertTrue(notifContent.item != null) - - database.itemDao().delete(item1).subscribe() - } - - @Test - fun testTwoAccountsWithOneFeedNotificationEnabled() { - val item1 = Item().apply { - title = "testTwoAccountsWithOneAccountNotificationsEnabled" - feedId = 1 - remoteId = "remoteId 1" - pubDate = LocalDateTime.now() - } - - val item2 = Item().apply { - title = "testTwoAccountsWithOneAccountNotificationsEnabled2" - feedId = 2 - } - - val item3 = Item().apply { - title = "testTwoAccountsWithOneAccountNotificationsEnabled3" - feedId = 2 - } - - database.itemDao().insert(item1).subscribe() - - val syncResult1 = SyncResult().apply { items = listOf(item1) } - val syncResult2 = SyncResult().apply { items = listOf(item2, item3) } - - val syncResults = mutableMapOf().apply { - put(account1, syncResult1) - put(account2, syncResult2) - } - - val notifContent = SyncResultAnalyser(context, syncResults, database).getSyncNotifContent() - - assertEquals("testTwoAccountsWithOneAccountNotificationsEnabled", notifContent.content) - assertEquals("feed 1", notifContent.title) - assertTrue(notifContent.largeIcon != null) - assertTrue(notifContent.item != null) - - database.itemDao().delete(item1).subscribe() - } - - - @Test - fun testOneAccountTwoFeedsWithOneFeedNotificationEnabled() { - val item1 = Item().apply { - title = "testTwoAccountsWithOneAccountNotificationsEnabled" - feedId = 1 - remoteId = "remoteId 1" - pubDate = LocalDateTime.now() - } - - val item2 = Item().apply { - title = "testTwoAccountsWithOneAccountNotificationsEnabled2" - feedId = 2 - } - - val item3 = Item().apply { - title = "testTwoAccountsWithOneAccountNotificationsEnabled3" - feedId = 2 - } - - database.itemDao().insert(item1).subscribe() - - val syncResult = SyncResult().apply { items = listOf(item1, item2, item3) } - val notifContent = SyncResultAnalyser(context, mapOf(Pair(account1, syncResult)), database).getSyncNotifContent() - - assertEquals("testTwoAccountsWithOneAccountNotificationsEnabled", notifContent.content) - assertEquals("feed 1", notifContent.title) - assertTrue(notifContent.largeIcon != null) - assertTrue(notifContent.item != null) - assertTrue(notifContent.accountId!! > 0) - - database.itemDao().delete(item1).subscribe() - } -} \ No newline at end of file diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 548b2db7..00000000 --- a/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - diff --git a/app/src/debug/java/com/readrops/app/ReadropsDebugApp.java b/app/src/debug/java/com/readrops/app/ReadropsDebugApp.java deleted file mode 100644 index 1eb6475d..00000000 --- a/app/src/debug/java/com/readrops/app/ReadropsDebugApp.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.readrops.app; - -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.work.Configuration; - -import com.facebook.flipper.android.AndroidFlipperClient; -import com.facebook.flipper.android.utils.FlipperUtils; -import com.facebook.flipper.core.FlipperClient; -import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; -import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; -import com.facebook.flipper.plugins.inspector.DescriptorMapping; -import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; -import com.facebook.flipper.plugins.navigation.NavigationFlipperPlugin; -import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; -import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; -import com.facebook.soloader.SoLoader; - -public class ReadropsDebugApp extends ReadropsApp implements Configuration.Provider { - - @Override - public void onCreate() { - super.onCreate(); - SoLoader.init(this, false); - - //initFlipper(); - } - - private void initFlipper() { - if (FlipperUtils.shouldEnableFlipper(this)) { - FlipperClient client = AndroidFlipperClient.getInstance(this); - client.addPlugin(new InspectorFlipperPlugin(this, DescriptorMapping.withDefaults())); - - NetworkFlipperPlugin networkPlugin = new NetworkFlipperPlugin(); - client.addPlugin(networkPlugin); - - client.addPlugin(new DatabasesFlipperPlugin(this)); - client.addPlugin(CrashReporterPlugin.getInstance()); - client.addPlugin(NavigationFlipperPlugin.getInstance()); - client.addPlugin(new SharedPreferencesFlipperPlugin(this)); - - client.start(); - } - } - - @NonNull - @Override - public Configuration getWorkManagerConfiguration() { - return new Configuration.Builder() - .setMinimumLoggingLevel(Log.DEBUG) - .build(); - } -} - diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml deleted file mode 100644 index 660a84f4..00000000 --- a/app/src/debug/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - ReadropsDebug - diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml deleted file mode 100644 index 7c4c79f9..00000000 --- a/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/AppModule.kt b/app/src/main/java/com/readrops/app/AppModule.kt deleted file mode 100644 index 8a50624d..00000000 --- a/app/src/main/java/com/readrops/app/AppModule.kt +++ /dev/null @@ -1,75 +0,0 @@ -package com.readrops.app - -import androidx.preference.PreferenceManager -import com.readrops.api.services.Credentials -import com.readrops.app.account.AccountViewModel -import com.readrops.app.addfeed.AddFeedsViewModel -import com.readrops.app.feedsfolders.ManageFeedsFoldersViewModel -import com.readrops.app.item.ItemViewModel -import com.readrops.app.itemslist.MainViewModel -import com.readrops.app.notifications.NotificationPermissionViewModel -import com.readrops.app.repositories.FreshRSSRepository -import com.readrops.app.repositories.LocalFeedRepository -import com.readrops.app.repositories.NextNewsRepository -import com.readrops.app.utils.GlideApp -import com.readrops.db.entities.account.Account -import com.readrops.db.entities.account.AccountType -import org.koin.android.ext.koin.androidApplication -import org.koin.android.ext.koin.androidContext -import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.core.parameter.parametersOf -import org.koin.dsl.module - -val appModule = module { - - factory { (account: Account) -> - when (account.accountType) { - AccountType.LOCAL -> LocalFeedRepository(get(), get(), androidContext(), account) - AccountType.NEXTCLOUD_NEWS -> NextNewsRepository(get(parameters = { parametersOf(Credentials.toCredentials(account)) }), - get(), androidContext(), account) - AccountType.FRESHRSS -> FreshRSSRepository(get(parameters = { parametersOf(Credentials.toCredentials(account)) }), - get(), androidContext(), account) - else -> throw IllegalArgumentException("Account type not supported") - } - } - - viewModel { - MainViewModel(get()) - } - - viewModel { - AddFeedsViewModel(get(), get()) - } - - viewModel { - ItemViewModel(get()) - } - - viewModel { - ManageFeedsFoldersViewModel(get()) - } - - viewModel { - NotificationPermissionViewModel(get()) - } - - viewModel { - AccountViewModel(get()) - } - - single { GlideApp.with(androidApplication()) } - - single { PreferenceManager.getDefaultSharedPreferences(androidContext()) } - - /* single { - val niddler = AndroidNiddler.Builder() - .setNiddlerInformation(AndroidNiddler.fromApplication(get())) - .setPort(0) - .setMaxStackTraceSize(10) - .build() - - niddler.attachToApplication(get()) - - niddler.apply { start() } - }*/ -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/ReadropsApp.kt b/app/src/main/java/com/readrops/app/ReadropsApp.kt deleted file mode 100644 index cf2fadb8..00000000 --- a/app/src/main/java/com/readrops/app/ReadropsApp.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.readrops.app - -import android.app.Application -import android.app.NotificationChannel -import android.app.NotificationManager -import android.os.Build -import androidx.appcompat.app.AppCompatDelegate -import androidx.preference.PreferenceManager -import com.readrops.api.apiModule -import com.readrops.app.utils.SharedPreferencesManager -import com.readrops.db.dbModule -import io.reactivex.plugins.RxJavaPlugins -import org.koin.android.ext.koin.androidContext -import org.koin.android.ext.koin.androidLogger -import org.koin.core.context.startKoin -import org.koin.core.logger.Level - -open class ReadropsApp : Application() { - - override fun onCreate() { - super.onCreate() - RxJavaPlugins.setErrorHandler { e: Throwable? -> } - - createNotificationChannels() - PreferenceManager.setDefaultValues(this, R.xml.preferences, false) - - startKoin { - androidLogger(Level.ERROR) - androidContext(this@ReadropsApp) - - modules(apiModule, dbModule, appModule) - } - - val theme = when (SharedPreferencesManager.readString(SharedPreferencesManager.SharedPrefKey.DARK_THEME)) { - getString(R.string.theme_value_light) -> AppCompatDelegate.MODE_NIGHT_NO - getString(R.string.theme_value_dark) -> AppCompatDelegate.MODE_NIGHT_YES - else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - } - - AppCompatDelegate.setDefaultNightMode(theme) - } - - private fun createNotificationChannels() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val feedsColorsChannel = NotificationChannel(FEEDS_COLORS_CHANNEL_ID, - getString(R.string.feeds_colors), NotificationManager.IMPORTANCE_DEFAULT) - feedsColorsChannel.description = getString(R.string.get_feeds_colors) - - val opmlExportChannel = NotificationChannel(OPML_EXPORT_CHANNEL_ID, - getString(R.string.opml_export), NotificationManager.IMPORTANCE_DEFAULT) - opmlExportChannel.description = getString(R.string.opml_export_description) - - val syncChannel = NotificationChannel(SYNC_CHANNEL_ID, - getString(R.string.auto_synchro), NotificationManager.IMPORTANCE_LOW) - syncChannel.description = getString(R.string.account_synchro) - - val manager = getSystemService(NotificationManager::class.java)!! - - manager.createNotificationChannel(feedsColorsChannel) - manager.createNotificationChannel(opmlExportChannel) - manager.createNotificationChannel(syncChannel) - } - } - - companion object { - const val FEEDS_COLORS_CHANNEL_ID = "feedsColorsChannel" - const val OPML_EXPORT_CHANNEL_ID = "opmlExportChannel" - const val SYNC_CHANNEL_ID = "syncChannel" - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/account/AccountTypeListActivity.java b/app/src/main/java/com/readrops/app/account/AccountTypeListActivity.java deleted file mode 100644 index 528ceae7..00000000 --- a/app/src/main/java/com/readrops/app/account/AccountTypeListActivity.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.readrops.app.account; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcelable; -import android.util.Log; -import android.view.MenuItem; -import android.view.View; -import android.widget.LinearLayout; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.readrops.app.utils.OPMLHelper; -import com.readrops.app.R; -import com.readrops.app.databinding.ActivityAccountTypeListBinding; -import com.readrops.app.itemslist.MainActivity; -import com.readrops.app.utils.Utils; -import com.readrops.db.entities.account.Account; -import com.readrops.db.entities.account.AccountType; - -import java.util.ArrayList; -import java.util.List; - -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.observers.DisposableCompletableObserver; -import io.reactivex.observers.DisposableSingleObserver; -import io.reactivex.schedulers.Schedulers; - -import static com.readrops.app.utils.OPMLHelper.OPEN_OPML_FILE_REQUEST; -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT; -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT_TYPE; -import static com.readrops.app.utils.ReadropsKeys.FROM_MAIN_ACTIVITY; - -import org.koin.android.compat.ViewModelCompat; - -public class AccountTypeListActivity extends AppCompatActivity { - - private static final String TAG = AccountTypeListActivity.class.getSimpleName(); - - private ActivityAccountTypeListBinding binding; - private AccountTypeListAdapter adapter; - private AccountViewModel viewModel; - - private boolean fromMainActivity; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - binding = ActivityAccountTypeListBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - viewModel = ViewModelCompat.getViewModel(this, AccountViewModel.class); - - setTitle(R.string.new_account); - - binding.accountTypeRecyclerview.setLayoutManager(new LinearLayoutManager(this)); - binding.accountTypeRecyclerview.addItemDecoration(new DividerItemDecoration(this, LinearLayout.VERTICAL)); - - fromMainActivity = getIntent().getBooleanExtra(FROM_MAIN_ACTIVITY, false); - - if (fromMainActivity) - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - adapter = new AccountTypeListAdapter(accountType -> { - if (accountType != AccountType.LOCAL) { - Intent intent = new Intent(getApplicationContext(), AddAccountActivity.class); - - if (fromMainActivity) - intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); - - intent.putExtra(ACCOUNT_TYPE, (Parcelable) accountType); - - startActivity(intent); - finish(); - } else { - Account account = new Account(null, getString(AccountType.LOCAL.getTypeName()), AccountType.LOCAL); - account.setCurrentAccount(true); - - viewModel.insert(account) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new DisposableSingleObserver() { - @Override - public void onSuccess(Long id) { - account.setId(id.intValue()); - goToNextActivity(account); - } - - @Override - public void onError(Throwable e) { - Log.e(TAG, e.getMessage()); - Utils.showSnackbar(binding.accountTypeListRoot, e.getMessage()); - } - }); - } - - }); - - binding.accountTypeRecyclerview.setAdapter(adapter); - adapter.setAccountTypes(getData()); - } - - private List getData() { - List accountTypes = new ArrayList<>(); - - accountTypes.add(AccountType.LOCAL); - accountTypes.add(AccountType.NEXTCLOUD_NEWS); - accountTypes.add(AccountType.FRESHRSS); - - return accountTypes; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - } - - return super.onOptionsItemSelected(item); - } - - public void openOPMLFile(View view) { - OPMLHelper.openFileIntent(this); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - if (requestCode == OPEN_OPML_FILE_REQUEST && resultCode == RESULT_OK && data != null) { - Uri uri = data.getData(); - - MaterialDialog dialog = new MaterialDialog.Builder(this) - .title(R.string.opml_processing) - .content(R.string.operation_takes_time) - .progress(true, 100) - .cancelable(false) - .show(); - - parseOPMLFile(uri, dialog); - } - - super.onActivityResult(requestCode, resultCode, data); - } - - private void parseOPMLFile(Uri uri, MaterialDialog dialog) { - Account account = new Account(null, getString(AccountType.LOCAL.getTypeName()), AccountType.LOCAL); - account.setCurrentAccount(true); - - viewModel.insert(account) - .flatMapCompletable(id -> { - account.setId(id.intValue()); - viewModel.setAccount(account); - - return viewModel.parseOPMLFile(uri, this); - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new DisposableCompletableObserver() { - @Override - public void onComplete() { - dialog.dismiss(); - goToNextActivity(account); - } - - @Override - public void onError(Throwable e) { - Log.e(TAG, e.getMessage()); - - dialog.dismiss(); - Utils.showSnackbar(binding.accountTypeListRoot, e.getMessage()); - } - }); - } - - private void goToNextActivity(Account account) { - if (fromMainActivity) { - Intent intent = new Intent(); - intent.putExtra(ACCOUNT, account); - setResult(RESULT_OK, intent); - } else { - Intent intent = new Intent(getApplicationContext(), MainActivity.class); - intent.putExtra(ACCOUNT, account); - - startActivity(intent); - } - - finish(); - } -} diff --git a/app/src/main/java/com/readrops/app/account/AccountTypeListAdapter.java b/app/src/main/java/com/readrops/app/account/AccountTypeListAdapter.java deleted file mode 100644 index 1d03e17d..00000000 --- a/app/src/main/java/com/readrops/app/account/AccountTypeListAdapter.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.readrops.app.account; - -import android.view.LayoutInflater; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.readrops.app.databinding.AccountTypeItemBinding; -import com.readrops.db.entities.account.AccountType; - -import java.util.List; - -public class AccountTypeListAdapter extends RecyclerView.Adapter { - - private List accountTypes; - private OnItemClickListener listener; - - public AccountTypeListAdapter(OnItemClickListener listener) { - this.listener = listener; - } - - @NonNull - @Override - public AccountTypeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - AccountTypeItemBinding binding = AccountTypeItemBinding.inflate(LayoutInflater.from(parent.getContext()), - parent, false); - - return new AccountTypeViewHolder(binding); - } - - @Override - public void onBindViewHolder(@NonNull AccountTypeViewHolder holder, int position) { - AccountType accountType = accountTypes.get(position); - - holder.binding.accountTypeName.setText(accountType.getTypeName()); - holder.binding.accountTypeLogo.setImageResource(accountType.getIconRes()); - - holder.binding.getRoot().setOnClickListener(v -> listener.onItemClick(accountType)); - } - - @Override - public int getItemCount() { - return accountTypes.size(); - } - - public void setAccountTypes(List accountTypes) { - this.accountTypes = accountTypes; - notifyDataSetChanged(); - } - - public interface OnItemClickListener { - void onItemClick(AccountType accountType); - } - - public class AccountTypeViewHolder extends RecyclerView.ViewHolder { - - private AccountTypeItemBinding binding; - - public AccountTypeViewHolder(AccountTypeItemBinding binding) { - super(binding.getRoot()); - - this.binding = binding; - } - } -} diff --git a/app/src/main/java/com/readrops/app/account/AccountViewModel.java b/app/src/main/java/com/readrops/app/account/AccountViewModel.java deleted file mode 100644 index 99a37586..00000000 --- a/app/src/main/java/com/readrops/app/account/AccountViewModel.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.readrops.app.account; - -import android.content.Context; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.lifecycle.ViewModel; - -import com.readrops.api.opml.OPMLParser; -import com.readrops.app.repositories.ARepository; -import com.readrops.db.Database; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.Folder; -import com.readrops.db.entities.account.Account; - -import org.koin.core.parameter.ParametersHolderKt; -import org.koin.java.KoinJavaComponent; - -import java.io.FileNotFoundException; -import java.util.List; -import java.util.Map; - -import io.reactivex.Completable; -import io.reactivex.Single; - -public class AccountViewModel extends ViewModel { - - private ARepository repository; - private final Database database; - - public AccountViewModel(@NonNull Database database) { - this.database = database; - } - - public void setAccount(Account account) { - repository = KoinJavaComponent.get(ARepository.class, null, - () -> ParametersHolderKt.parametersOf(account)); - } - - public Completable login(Account account, boolean insert) { - setAccount(account); - return repository.login(account, insert); - } - - public Single insert(Account account) { - return database.accountDao().insert(account); - } - - public Completable update(Account account) { - return database.accountDao().update(account); - } - - public Completable delete(Account account) { - return database.accountDao().delete(account); - } - - public Single getAccountCount() { - return database.accountDao().getAccountCount(); - } - - @SuppressWarnings("unchecked") - public Single>> getFoldersWithFeeds() { - return repository.getFoldersWithFeeds(); - } - - public Completable parseOPMLFile(Uri uri, Context context) throws FileNotFoundException { - /*return OPMLParser.read(context.getContentResolver().openInputStream(uri)) - .flatMapCompletable(foldersAndFeeds -> repository.insertOPMLFoldersAndFeeds(foldersAndFeeds));*/ - return Completable.complete(); - } -} diff --git a/app/src/main/java/com/readrops/app/account/AddAccountActivity.java b/app/src/main/java/com/readrops/app/account/AddAccountActivity.java deleted file mode 100644 index b68ff641..00000000 --- a/app/src/main/java/com/readrops/app/account/AddAccountActivity.java +++ /dev/null @@ -1,239 +0,0 @@ -package com.readrops.app.account; - -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.util.Patterns; -import android.view.KeyEvent; -import android.view.MenuItem; -import android.view.View; - -import androidx.appcompat.app.AppCompatActivity; - -import com.readrops.app.R; -import com.readrops.app.databinding.ActivityAddAccountBinding; -import com.readrops.app.itemslist.MainActivity; -import com.readrops.app.utils.SharedPreferencesManager; -import com.readrops.app.utils.Utils; -import com.readrops.db.entities.account.Account; -import com.readrops.db.entities.account.AccountType; - -import io.reactivex.CompletableObserver; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; - -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT; -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT_TYPE; -import static com.readrops.app.utils.ReadropsKeys.EDIT_ACCOUNT; - -import org.koin.android.compat.ViewModelCompat; - -public class AddAccountActivity extends AppCompatActivity { - - private static final String TAG = AddAccountActivity.class.getSimpleName(); - - private ActivityAddAccountBinding binding; - private AccountViewModel viewModel; - - private AccountType accountType; - private boolean forwardResult, editAccount; - - private Account accountToEdit; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - binding = ActivityAddAccountBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - viewModel = ViewModelCompat.getViewModel(this, AccountViewModel.class); - - accountType = getIntent().getParcelableExtra(ACCOUNT_TYPE); - - int flag = getIntent().getFlags(); - forwardResult = flag == Intent.FLAG_ACTIVITY_FORWARD_RESULT; - - accountToEdit = getIntent().getParcelableExtra(EDIT_ACCOUNT); - - if (forwardResult || accountToEdit != null) - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - if (accountToEdit != null) { - editAccount = true; - fillFields(); - } else { - binding.providerImage.setImageResource(accountType.getIconRes()); - binding.providerName.setText(accountType.getTypeName()); - binding.addAccountName.setText(accountType.getTypeName()); - - if (accountType == AccountType.FRESHRSS) { - binding.addAccountPasswordLayout.setHelperText(getString(R.string.password_helper)); - } - } - } - - public void createAccount(View view) { - if (fieldsAreValid()) { - String url = binding.addAccountUrl.getText().toString().trim(); - String name = binding.addAccountName.getText().toString().trim(); - String login = binding.addAccountLogin.getText().toString().trim(); - String password = binding.addAccountPassword.getText().toString().trim(); - - if (!(url.toLowerCase().contains(Utils.HTTP_PREFIX) || url.toLowerCase().contains(Utils.HTTPS_PREFIX))) { - url = Utils.HTTPS_PREFIX + url; - } - - if (editAccount) { - accountToEdit.setUrl(url); - accountToEdit.setAccountName(name); - accountToEdit.setLogin(login); - accountToEdit.setPassword(password); - - updateAccount(); - } else { - Account account = new Account(url, name, accountType); - account.setLogin(login); - account.setPassword(password); - - viewModel.login(account, true) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new CompletableObserver() { - - @Override - public void onSubscribe(Disposable d) { - binding.addAccountLoading.setVisibility(View.VISIBLE); - binding.addAccountValidate.setEnabled(false); - } - - @Override - public void onComplete() { - saveLoginPassword(account); - - if (forwardResult) { - Intent intent = new Intent(); - intent.putExtra(ACCOUNT, account); - setResult(RESULT_OK, intent); - } else { - Intent intent = new Intent(getApplicationContext(), MainActivity.class); - intent.putExtra(ACCOUNT, account); - startActivity(intent); - } - - finish(); - } - - @Override - public void onError(Throwable e) { - Log.d(TAG, e.getMessage()); - binding.addAccountLoading.setVisibility(View.GONE); - binding.addAccountValidate.setEnabled(true); - - Utils.showSnackbar(binding.addAccountRoot, e.getMessage()); - } - }); - } - - } - } - - private boolean fieldsAreValid() { - boolean valid = true; - - if (binding.addAccountUrl.getText().toString().trim().isEmpty()) { - binding.addAccountUrl.setError(getString(R.string.empty_field)); - valid = false; - } else if (!Patterns.WEB_URL.matcher(binding.addAccountUrl.getText().toString().trim()).matches()) { - binding.addAccountUrl.setError(getString(R.string.wrong_url)); - valid = false; - } - - if (binding.addAccountName.getText().toString().trim().isEmpty()) { - binding.addAccountName.setError(getString(R.string.empty_field)); - valid = false; - } - - if (binding.addAccountLogin.getText().toString().trim().isEmpty()) { - binding.addAccountLogin.setError(getString(R.string.empty_field)); - valid = false; - } - - if (binding.addAccountPassword.getText().toString().trim().isEmpty()) { - binding.addAccountPassword.setError(getString(R.string.empty_field)); - valid = false; - } - - return valid; - } - - private void saveLoginPassword(Account account) { - SharedPreferencesManager.writeValue(account.getLoginKey(), account.getLogin()); - SharedPreferencesManager.writeValue(account.getPasswordKey(), account.getPassword()); - - account.setLogin(null); - account.setPassword(null); - } - - private void fillFields() { - binding.providerImage.setImageResource(accountToEdit.getAccountType().getIconRes()); - binding.providerName.setText(accountToEdit.getAccountType().getTypeName()); - - binding.addAccountUrl.setText(accountToEdit.getUrl()); - binding.addAccountName.setText(accountToEdit.getAccountName()); - binding.addAccountLogin.setText(SharedPreferencesManager.readString(accountToEdit.getLoginKey())); - binding.addAccountPassword.setText(SharedPreferencesManager.readString(accountToEdit.getPasswordKey())); - } - - private void updateAccount() { - viewModel.login(accountToEdit, false) - .doOnError(throwable -> Utils.showSnackbar(binding.addAccountRoot, throwable.getMessage())) - .doAfterTerminate(() -> saveLoginPassword(accountToEdit)) - .andThen(viewModel.update(accountToEdit)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new CompletableObserver() { - @Override - public void onSubscribe(Disposable d) { - binding.addAccountLoading.setVisibility(View.VISIBLE); - binding.addAccountValidate.setEnabled(false); - } - - @Override - public void onComplete() { - finish(); - } - - @Override - public void onError(Throwable e) { - binding.addAccountLoading.setVisibility(View.GONE); - binding.addAccountValidate.setEnabled(true); - - Utils.showSnackbar(binding.addAccountRoot, e.getMessage()); - } - }); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - } - - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - switch (keyCode) { - case KeyEvent.KEYCODE_ENTER: - createAccount(null); - return true; - } - - return super.onKeyUp(keyCode, event); - } -} diff --git a/app/src/main/java/com/readrops/app/addfeed/AccountArrayAdapter.java b/app/src/main/java/com/readrops/app/addfeed/AccountArrayAdapter.java deleted file mode 100644 index 5be62acf..00000000 --- a/app/src/main/java/com/readrops/app/addfeed/AccountArrayAdapter.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.readrops.app.addfeed; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.readrops.app.R; -import com.readrops.db.entities.account.Account; - -import java.util.List; - -public class AccountArrayAdapter extends ArrayAdapter { - - public AccountArrayAdapter(@NonNull Context context, @NonNull List objects) { - super(context, 0, objects); - } - - @Override - public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - return createItemView(position, convertView, parent); - } - - @NonNull - @Override - public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - return createItemView(position, convertView, parent); - } - - private View createItemView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = LayoutInflater.from(getContext()).inflate(R.layout.account_type_item, parent, false); - } - - Account account = getItem(position); - - ImageView accountIcon = convertView.findViewById(R.id.account_type_logo); - TextView accountName = convertView.findViewById(R.id.account_type_name); - - accountIcon.setImageResource(account.getAccountType().getIconRes()); - accountName.setText(account.getAccountType().getTypeName()); - - return convertView; - } -} diff --git a/app/src/main/java/com/readrops/app/addfeed/AddFeedActivity.java b/app/src/main/java/com/readrops/app/addfeed/AddFeedActivity.java deleted file mode 100644 index bd0dea1a..00000000 --- a/app/src/main/java/com/readrops/app/addfeed/AddFeedActivity.java +++ /dev/null @@ -1,333 +0,0 @@ -package com.readrops.app.addfeed; - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.graphics.Color; -import android.os.Bundle; -import android.util.Patterns; -import android.view.KeyEvent; -import android.view.MenuItem; -import android.view.View; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.mikepenz.fastadapter.FastAdapter; -import com.mikepenz.fastadapter.adapters.ItemAdapter; -import com.mikepenz.fastadapter.commons.utils.DiffCallback; -import com.mikepenz.fastadapter.commons.utils.FastAdapterDiffUtil; -import com.readrops.app.R; -import com.readrops.app.databinding.ActivityAddFeedBinding; -import com.readrops.app.utils.customviews.ReadropsItemTouchCallback; -import com.readrops.app.utils.SharedPreferencesManager; -import com.readrops.app.utils.Utils; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.account.Account; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.observers.DisposableSingleObserver; -import io.reactivex.schedulers.Schedulers; - -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT_ID; -import static com.readrops.app.utils.ReadropsKeys.FEEDS; - -import org.koin.android.compat.ViewModelCompat; - -public class AddFeedActivity extends AppCompatActivity implements View.OnClickListener { - - private AccountArrayAdapter arrayAdapter; - - private ItemAdapter parseItemsAdapter; - private ItemAdapter insertionResultsAdapter; - FastAdapter fastAdapter; - - private AddFeedsViewModel viewModel; - private ArrayList feedsToUpdate; - - private ActivityAddFeedBinding binding; - - @SuppressLint("ClickableViewAccessibility") - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - binding = ActivityAddFeedBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - binding.addFeedLoad.setOnClickListener(this); - binding.addFeedOk.setOnClickListener(this); - binding.addFeedOk.setEnabled(false); - - viewModel = ViewModelCompat.getViewModel(this, AddFeedsViewModel.class); - - parseItemsAdapter = new ItemAdapter<>(); - fastAdapter = FastAdapter.with(parseItemsAdapter); - fastAdapter.withSelectable(true); - fastAdapter.withOnClickListener((v, adapter, item, position) -> { - item.setChecked(!item.isChecked()); - - fastAdapter.notifyAdapterItemChanged(position); - binding.addFeedOk.setEnabled(recyclerViewHasCheckedItems()); - - return true; - }); - - binding.addFeedResults.setAdapter(fastAdapter); - RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this); - binding.addFeedResults.setLayoutManager(layoutManager); - - new ItemTouchHelper(new ReadropsItemTouchCallback(this, - new ReadropsItemTouchCallback.Config.Builder() - .swipeDirs(ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) - .leftDraw(Color.RED, R.drawable.ic_delete, null) - .rightDraw(Color.RED, R.drawable.ic_delete, null) - .swipeCallback((viewHolder, direction) -> { - parseItemsAdapter.remove(viewHolder.getAdapterPosition()); - - if (parseItemsAdapter.getAdapterItemCount() == 0) { - binding.addFeedResultsTextView.setVisibility(View.GONE); - binding.addFeedResults.setVisibility(View.GONE); - } - }) - .build())) - .attachToRecyclerView(binding.addFeedResults); - - insertionResultsAdapter = new ItemAdapter<>(); - RecyclerView.LayoutManager layoutManager1 = new LinearLayoutManager(this); - binding.addFeedInsertedResultsRecyclerview.setAdapter(FastAdapter.with(insertionResultsAdapter)); - binding.addFeedInsertedResultsRecyclerview.setLayoutManager(layoutManager1); - - viewModel.getAccounts().observe(this, accounts -> { - // set the current account at the top of the list - int currentAccountId = getIntent().getIntExtra(ACCOUNT_ID, 1); - Collections.sort(accounts, (o1, o2) -> { - if (o1.getId() == currentAccountId) { - return -1; - } else if (o2.getId() == currentAccountId) { - return 1; - } else { - return 0; - } - }); - - arrayAdapter = new AccountArrayAdapter(this, accounts); - arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - - binding.addFeedAccountSpinner.setAdapter(arrayAdapter); - }); - - feedsToUpdate = new ArrayList<>(); - - // new feed intent - if (getIntent().getAction() != null && getIntent().getAction().equals(Intent.ACTION_SEND)) { - String text = getIntent().getStringExtra(Intent.EXTRA_TEXT); - binding.addFeedTextInput.setText(text); - onClick(binding.addFeedLoad); - } - } - - @Override - public void onClick(View v) { - if (v.getId() == R.id.add_feed_load) { - if (isValidUrl()) { - binding.addFeedLoadingMessage.setVisibility(View.GONE); - binding.addFeedLoading.setVisibility(View.VISIBLE); - loadFeed(); - } - } else if (v.getId() == R.id.add_feed_ok) { - insertionResultsAdapter.clear(); - insertFeeds(); - } - } - - private boolean isValidUrl() { - String url = binding.addFeedTextInput.getText().toString().trim(); - - if (url.isEmpty()) { - binding.addFeedTextInput.setError(getString(R.string.empty_field)); - return false; - } else if (!Patterns.WEB_URL.matcher(url).matches()) { - binding.addFeedTextInput.setError(getString(R.string.wrong_url)); - return false; - } else - return true; - } - - private boolean recyclerViewHasCheckedItems() { - for (ParsingResult result : parseItemsAdapter.getAdapterItems()) { - if (result.isChecked()) - return true; - } - - return false; - } - - private void disableParsingResult(ParsingResult parsingResult) { - for (ParsingResult result : parseItemsAdapter.getAdapterItems()) { - if (result.getUrl().equals(parsingResult.getUrl())) { - result.setChecked(false); - fastAdapter.notifyAdapterItemChanged(parseItemsAdapter.getAdapterPosition(result)); - } - } - } - - private void loadFeed() { - String url = binding.addFeedTextInput.getText().toString().trim(); - - final String finalUrl; - if (!(url.contains(Utils.HTTP_PREFIX) || url.contains(Utils.HTTPS_PREFIX))) - finalUrl = Utils.HTTPS_PREFIX + url; - else - finalUrl = url; - - viewModel.parseUrl(finalUrl) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new DisposableSingleObserver>() { - @Override - public void onSuccess(List parsingResultList) { - displayParseResults(parsingResultList); - } - - @Override - public void onError(Throwable e) { - Utils.showSnackbar(binding.addFeedRoot, e.getMessage()); - binding.addFeedLoading.setVisibility(View.GONE); - } - }); - } - - private void displayParseResults(List parsingResultList) { - binding.addFeedLoading.setVisibility(View.GONE); - - if (!parsingResultList.isEmpty()) { - binding.addFeedResultsTextView.setVisibility(View.VISIBLE); - binding.addFeedResults.setVisibility(View.VISIBLE); - - DiffUtil.DiffResult diffResult = FastAdapterDiffUtil.calculateDiff(parseItemsAdapter, parsingResultList, new DiffCallback() { - @Override - public boolean areItemsTheSame(ParsingResult oldItem, ParsingResult newItem) { - return oldItem.getUrl().equals(newItem.getUrl()); - } - - @Override - public boolean areContentsTheSame(ParsingResult oldItem, ParsingResult newItem) { - return oldItem.getUrl().equals(newItem.getUrl()) && - oldItem.isChecked() == newItem.isChecked(); - } - - @Nullable - @Override - public Object getChangePayload(ParsingResult oldItem, int oldItemPosition, ParsingResult newItem, int newItemPosition) { - newItem.setChecked(oldItem.isChecked()); - return newItem; - } - }, false); - - FastAdapterDiffUtil.set(parseItemsAdapter, diffResult); - binding.addFeedOk.setEnabled(recyclerViewHasCheckedItems()); - } else { - parseItemsAdapter.clear(); - binding.addFeedResultsTextView.setVisibility(View.GONE); - binding.addFeedResults.setVisibility(View.GONE); - - binding.addFeedLoadingMessage.setVisibility(View.VISIBLE); - binding.addFeedLoadingMessage.setText(R.string.no_feed_found); - } - } - - private void insertFeeds() { - binding.addFeedInsertProgressbar.setVisibility(View.VISIBLE); - binding.addFeedOk.setEnabled(false); - - List feedsToInsert = new ArrayList<>(); - for (ParsingResult result : parseItemsAdapter.getAdapterItems()) { - if (result.isChecked()) - feedsToInsert.add(result); - } - - Account account = (Account) binding.addFeedAccountSpinner.getSelectedItem(); - - account.setLogin(SharedPreferencesManager.readString(account.getLoginKey())); - account.setPassword(SharedPreferencesManager.readString(account.getPasswordKey())); - - viewModel.addFeeds(feedsToInsert, account) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new DisposableSingleObserver>() { - @Override - public void onSuccess(List feedInsertionResults) { - displayInsertionResults(feedInsertionResults); - } - - @Override - public void onError(Throwable e) { - binding.addFeedInsertProgressbar.setVisibility(View.GONE); - binding.addFeedOk.setEnabled(true); - Utils.showSnackbar(binding.addFeedRoot, e.getMessage()); - } - }); - } - - private void displayInsertionResults(List feedInsertionResults) { - binding.addFeedInsertProgressbar.setVisibility(View.GONE); - binding.addFeedInsertedResultsRecyclerview.setVisibility(View.VISIBLE); - - for (FeedInsertionResult feedInsertionResult : feedInsertionResults) { - if (feedInsertionResult.getFeed() != null) - feedsToUpdate.add(feedInsertionResult.getFeed()); - - disableParsingResult(feedInsertionResult.getParsingResult()); - } - - insertionResultsAdapter.add(feedInsertionResults); - binding.addFeedOk.setEnabled(recyclerViewHasCheckedItems()); - } - - - @Override - public void onBackPressed() { - finish(); - super.onBackPressed(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } - - return super.onOptionsItemSelected(item); - } - - @Override - public void finish() { - if (!feedsToUpdate.isEmpty()) { - Intent intent = new Intent(); - intent.putParcelableArrayListExtra(FEEDS, feedsToUpdate); - - setResult(RESULT_OK, intent); - } - - super.finish(); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_ENTER) { - onClick(binding.addFeedLoad); - return true; - } - - return super.onKeyUp(keyCode, event); - } -} diff --git a/app/src/main/java/com/readrops/app/addfeed/AddFeedsViewModel.java b/app/src/main/java/com/readrops/app/addfeed/AddFeedsViewModel.java deleted file mode 100644 index 33dc7c66..00000000 --- a/app/src/main/java/com/readrops/app/addfeed/AddFeedsViewModel.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.readrops.app.addfeed; - -import androidx.annotation.NonNull; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.ViewModel; - -import com.readrops.api.localfeed.LocalRSSDataSource; -import com.readrops.app.repositories.ARepository; -import com.readrops.app.utils.HtmlParser; -import com.readrops.db.Database; -import com.readrops.db.entities.account.Account; - -import org.koin.core.parameter.ParametersHolderKt; -import org.koin.java.KoinJavaComponent; - -import java.util.ArrayList; -import java.util.List; - -import io.reactivex.Single; - -public class AddFeedsViewModel extends ViewModel { - - private final Database database; - private final LocalRSSDataSource localRSSDataSource; - - public AddFeedsViewModel(@NonNull Database database, @NonNull LocalRSSDataSource localRSSDataSource) { - this.database = database; - this.localRSSDataSource = localRSSDataSource; - } - - public Single> addFeeds(List results, Account account) { - ARepository repository = KoinJavaComponent.get(ARepository.class, null, - () -> ParametersHolderKt.parametersOf(account)); - - return repository.addFeeds(results); - } - - public Single> parseUrl(String url) { - return Single.create(emitter -> { - List results = new ArrayList<>(); - - if (localRSSDataSource.isUrlRSSResource(url)) { - ParsingResult parsingResult = new ParsingResult(url, null); - results.add(parsingResult); - } else { - results.addAll(HtmlParser.getFeedLink(url)); - } - - emitter.onSuccess(results); - }); - } - - public LiveData> getAccounts() { - return database.accountDao().selectAllAsync(); - } -} diff --git a/app/src/main/java/com/readrops/app/addfeed/FeedInsertionResult.java b/app/src/main/java/com/readrops/app/addfeed/FeedInsertionResult.java deleted file mode 100644 index fd2e8379..00000000 --- a/app/src/main/java/com/readrops/app/addfeed/FeedInsertionResult.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.readrops.app.addfeed; - -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.StringRes; - -import com.mikepenz.fastadapter.FastAdapter; -import com.mikepenz.fastadapter.items.AbstractItem; -import com.readrops.app.R; -import com.readrops.db.entities.Feed; - -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class FeedInsertionResult extends AbstractItem { - - private Feed feed; - - private ParsingResult parsingResult; - - private FeedInsertionError insertionError; - - public FeedInsertionResult() { - // empty constructor - } - - public Feed getFeed() { - return feed; - } - - public void setFeed(Feed feed) { - this.feed = feed; - } - - public ParsingResult getParsingResult() { - return parsingResult; - } - - public void setParsingResult(ParsingResult parsingResult) { - this.parsingResult = parsingResult; - } - - public FeedInsertionError getInsertionError() { - return insertionError; - } - - public void setInsertionError(FeedInsertionError insertionError) { - this.insertionError = insertionError; - } - - @Override - public boolean isSelectable() { - return false; - } - - @NonNull - @Override - public FeedInsertionViewHolder getViewHolder(View v) { - return new FeedInsertionViewHolder(v); - } - - @Override - public int getType() { - return 0; - } - - @Override - public int getLayoutRes() { - return R.layout.feed_insertion_result; - } - - - public enum FeedInsertionError { - ERROR, - NETWORK_ERROR, - DB_ERROR, - PARSE_ERROR, - FORMAT_ERROR, - UNKNOWN_ERROR - } - - class FeedInsertionViewHolder extends FastAdapter.ViewHolder { - - private TextView feedInsertionRes; - private ImageView feedInsertionIcon; - - public FeedInsertionViewHolder(View itemView) { - super(itemView); - - feedInsertionRes = itemView.findViewById(R.id.feed_insertion_result_text_view); - feedInsertionIcon = itemView.findViewById(R.id.feed_insertion_result_icon); - } - - @Override - public void bindView(FeedInsertionResult item, List payloads) { - if (item.getInsertionError() == null) { - setText(R.string.feed_insertion_successfull, item.parsingResult); - feedInsertionIcon.setImageResource(R.drawable.ic_check_green); - } else { - switch (item.getInsertionError()) { - case NETWORK_ERROR: - setText(R.string.feed_insertion_network_failed, item.parsingResult); - break; - case DB_ERROR: - break; - case PARSE_ERROR: - setText(R.string.feed_insertion_parse_failed, item.parsingResult); - break; - case FORMAT_ERROR: - setText(R.string.feed_insertion_wrong_format, item.parsingResult); - break; - case UNKNOWN_ERROR: - setText(R.string.feed_insertion_unknown_error, item.parsingResult); - break; - case ERROR: - setText(R.string.feed_insertion_error, item.parsingResult); - } - - feedInsertionIcon.setImageResource(R.drawable.ic_warning_red); - } - } - - private void setText(@StringRes int stringRes, ParsingResult parsingResult) { - feedInsertionRes.setText(itemView.getContext().getString(stringRes, - parsingResult.getLabel() != null ? parsingResult.getLabel() : - parsingResult.getUrl())); - } - - @Override - public void unbindView(@NotNull FeedInsertionResult item) { - // not useful - } - } -} diff --git a/app/src/main/java/com/readrops/app/addfeed/ParsingResult.java b/app/src/main/java/com/readrops/app/addfeed/ParsingResult.java deleted file mode 100644 index 09e05489..00000000 --- a/app/src/main/java/com/readrops/app/addfeed/ParsingResult.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.readrops.app.addfeed; - -import android.view.View; -import android.widget.CheckBox; -import android.widget.TextView; - -import androidx.annotation.NonNull; - -import com.mikepenz.fastadapter.FastAdapter; -import com.mikepenz.fastadapter.items.AbstractItem; -import com.readrops.app.R; -import com.readrops.db.entities.Feed; - -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; - -public class ParsingResult extends AbstractItem { - - private String url; - - private String label; - - private boolean checked; - - private Integer folderId; - - public ParsingResult(String url, String label) { - this.url = url; - this.label = label; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getLabel() { - return label; - } - - public static List toParsingResults(List feeds) { - List parsingResults = new ArrayList<>(); - - for (Feed feed : feeds) { - ParsingResult parsingResult = new ParsingResult(feed.getUrl(), null); - parsingResult.setFolderId(feed.getFolderId()); - parsingResults.add(parsingResult); - } - - return parsingResults; - } - - public void setLabel(String label) { - this.label = label; - } - - public void setChecked(boolean checked) { - this.checked = checked; - } - - public boolean isChecked() { - return checked; - } - - public Integer getFolderId() { - return folderId; - } - - public void setFolderId(Integer folderId) { - this.folderId = folderId; - } - - @Override - public boolean isSelectable() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } - - @NonNull - @Override - public ParsingResultViewHolder getViewHolder(View v) { - return new ParsingResultViewHolder(v); - } - - @Override - public int getType() { - return R.id.add_feed_main_layout; - } - - @Override - public int getLayoutRes() { - return R.layout.add_feed_item; - } - - class ParsingResultViewHolder extends FastAdapter.ViewHolder { - - private TextView feedLabel; - private TextView feedUrl; - private CheckBox checkBox; - - public ParsingResultViewHolder(View itemView) { - super(itemView); - - feedLabel = itemView.findViewById(R.id.add_feed_item_label); - feedUrl = itemView.findViewById(R.id.add_feed_item_url); - checkBox = itemView.findViewById(R.id.add_feed_checkbox); - } - - @Override - public void bindView(@NotNull ParsingResult item, List payloads) { - if (!payloads.isEmpty()) { - ParsingResult newItem = (ParsingResult) payloads.get(0); - - checkBox.setChecked(newItem.isChecked()); - } else { - if (item.getLabel() != null && !item.getLabel().isEmpty()) - feedLabel.setText(item.getLabel()); - else - feedLabel.setVisibility(View.GONE); - - feedUrl.setText(item.getUrl()); - - checkBox.setChecked(item.isChecked()); - checkBox.setClickable(false); - } - - - } - - @Override - public void unbindView(@NotNull ParsingResult item) { - // not useful - } - } - - @Override - public boolean equals(Object o) { - if (o == null) - return false; - else if (!(o instanceof ParsingResult)) - return false; - else { - ParsingResult parsingResult = (ParsingResult) o; - - return parsingResult.getUrl().equals(this.getUrl()); - } - } -} diff --git a/app/src/main/java/com/readrops/app/feedsfolders/ManageFeedsFoldersActivity.java b/app/src/main/java/com/readrops/app/feedsfolders/ManageFeedsFoldersActivity.java deleted file mode 100644 index 3f346c44..00000000 --- a/app/src/main/java/com/readrops/app/feedsfolders/ManageFeedsFoldersActivity.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.readrops.app.feedsfolders; - -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentPagerAdapter; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.readrops.api.utils.exceptions.ConflictException; -import com.readrops.api.utils.exceptions.UnknownFormatException; -import com.readrops.app.R; -import com.readrops.app.databinding.ActivityManageFeedsFoldersBinding; -import com.readrops.app.feedsfolders.feeds.FeedsFragment; -import com.readrops.app.feedsfolders.folders.FoldersFragment; -import com.readrops.app.utils.Utils; -import com.readrops.db.entities.Folder; -import com.readrops.db.entities.account.Account; - -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; - -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT; - -import org.koin.android.compat.ViewModelCompat; - -public class ManageFeedsFoldersActivity extends AppCompatActivity { - - private ActivityManageFeedsFoldersBinding binding; - private ManageFeedsFoldersViewModel viewModel; - - private Account account; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - binding = ActivityManageFeedsFoldersBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - setSupportActionBar(binding.manageFeedsFoldersToolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - account = getIntent().getParcelableExtra(ACCOUNT); - - FeedsFoldersPageAdapter pageAdapter = new FeedsFoldersPageAdapter(getSupportFragmentManager()); - - binding.manageFeedsFoldersViewpager.setAdapter(pageAdapter); - binding.manageFeedsFoldersTablayout.setupWithViewPager(binding.manageFeedsFoldersViewpager); - - viewModel = ViewModelCompat.getViewModel(this, ManageFeedsFoldersViewModel.class); - viewModel.setAccount(account); - } - - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - if (account.getAccountType().getAccountConfig().getCanCreateFolder()) - getMenuInflater().inflate(R.menu.feeds_menu, menu); - - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - case R.id.add_folder: - addFolder(); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - public void onBackPressed() { - finish(); - super.onBackPressed(); - } - - private void addFolder() { - new MaterialDialog.Builder(ManageFeedsFoldersActivity.this) - .title(R.string.add_folder) - .positiveText(R.string.validate) - .input(R.string.folder, 0, (dialog, input) -> { - Folder folder = new Folder(); - folder.setName(input.toString()); - folder.setAccountId(account.getId()); - - viewModel.addFolder(folder) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(throwable -> { - String message; - if (throwable instanceof ConflictException) - message = getString(R.string.folder_already_exists); - else if (throwable instanceof UnknownFormatException) - message = getString(R.string.folder_bad_format); - else - message = getString(R.string.error_occured); - - Utils.showSnackbar(binding.manageFeedsFoldersRoot, message); - }) - .subscribe(); - }) - .show(); - } - - public class FeedsFoldersPageAdapter extends FragmentPagerAdapter { - - private FeedsFoldersPageAdapter(FragmentManager fragmentManager) { - super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); - } - - @Override - public int getCount() { - return 2; - } - - @Nullable - @Override - public CharSequence getPageTitle(int position) { - switch (position) { - case 0: - return getApplicationContext().getString(R.string.feeds); - case 1: - return getApplicationContext().getString(R.string.folders); - default: - return null; - } - } - - @Override - public Fragment getItem(int position) { - Fragment fragment = null; - - switch (position) { - case 0: - fragment = FeedsFragment.newInstance(account); - break; - case 1: - fragment = FoldersFragment.newInstance(account); - break; - } - - return fragment; - } - } -} diff --git a/app/src/main/java/com/readrops/app/feedsfolders/ManageFeedsFoldersViewModel.java b/app/src/main/java/com/readrops/app/feedsfolders/ManageFeedsFoldersViewModel.java deleted file mode 100644 index 344de635..00000000 --- a/app/src/main/java/com/readrops/app/feedsfolders/ManageFeedsFoldersViewModel.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.readrops.app.feedsfolders; - -import androidx.annotation.NonNull; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.ViewModel; - -import com.readrops.app.repositories.ARepository; -import com.readrops.db.Database; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.Folder; -import com.readrops.db.entities.account.Account; -import com.readrops.db.pojo.FeedWithFolder; -import com.readrops.db.pojo.FolderWithFeedCount; - -import org.koin.core.parameter.ParametersHolderKt; -import org.koin.java.KoinJavaComponent; - -import java.util.List; - -import io.reactivex.Completable; -import io.reactivex.Single; - -public class ManageFeedsFoldersViewModel extends ViewModel { - - private final Database database; - private LiveData> feedsWithFolder; - private LiveData> folders; - private ARepository repository; - - private Account account; - - public ManageFeedsFoldersViewModel(@NonNull Database database) { - this.database = database; - } - - private void setup() { - repository = KoinJavaComponent.get(ARepository.class, null, - () -> ParametersHolderKt.parametersOf(account)); - - feedsWithFolder = database.feedDao().getAllFeedsWithFolder(account.getId()); - folders = database.folderDao().getAllFolders(account.getId()); - } - - public LiveData> getFeedsWithFolder() { - return feedsWithFolder; - } - - public Completable updateFeedWithFolder(Feed feed) { - return repository.updateFeed(feed); - } - - public Account getAccount() { - return account; - } - - public void setAccount(Account account) { - this.account = account; - setup(); - } - - public LiveData> getFolders() { - return folders; - } - - public LiveData> getFoldersWithFeedCount() { - return database.folderDao().getFoldersWithFeedCount(account.getId()); - } - - public Single addFolder(Folder folder) { - return repository.addFolder(folder); - } - - public Completable updateFolder(Folder folder) { - return repository.updateFolder(folder); - } - - public Completable deleteFolder(Folder folder) { - return repository.deleteFolder(folder); - } - - public Completable deleteFeed(Feed feed) { - return repository.deleteFeed(feed); - } - - public Single getFeedCountByAccount() { - return database.feedDao().getFeedCount(account.getId()); - } -} diff --git a/app/src/main/java/com/readrops/app/feedsfolders/feeds/EditFeedDialogFragment.java b/app/src/main/java/com/readrops/app/feedsfolders/feeds/EditFeedDialogFragment.java deleted file mode 100644 index eb28c4f4..00000000 --- a/app/src/main/java/com/readrops/app/feedsfolders/feeds/EditFeedDialogFragment.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.readrops.app.feedsfolders.feeds; - -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT; - -import android.app.AlertDialog; -import android.app.Dialog; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Spinner; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.DialogFragment; - -import com.google.android.material.textfield.TextInputEditText; -import com.readrops.app.R; -import com.readrops.app.feedsfolders.ManageFeedsFoldersViewModel; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.Folder; -import com.readrops.db.entities.account.Account; -import com.readrops.db.pojo.FeedWithFolder; - -import org.koin.android.compat.SharedViewModelCompat; - -import java.util.ArrayList; -import java.util.Map; -import java.util.TreeMap; - -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; - -public class EditFeedDialogFragment extends DialogFragment implements AdapterView.OnItemSelectedListener { - - private TextInputEditText feedName; - private TextInputEditText feedUrl; - private Spinner folder; - - private Map values; - - private FeedWithFolder feedWithFolder; - private Account account; - private ManageFeedsFoldersViewModel viewModel; - - public EditFeedDialogFragment() { - } - - public static EditFeedDialogFragment newInstance(FeedWithFolder feedWithFolder, Account account) { - Bundle args = new Bundle(); - args.putParcelable("feedWithFolder", feedWithFolder); - args.putParcelable(ACCOUNT, account); - - EditFeedDialogFragment fragment = new EditFeedDialogFragment(); - fragment.setArguments(args); - return fragment; - } - - - @NonNull - @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - viewModel = SharedViewModelCompat.sharedViewModel(this, ManageFeedsFoldersViewModel.class).getValue(); - - feedWithFolder = getArguments().getParcelable("feedWithFolder"); - account = getArguments().getParcelable(ACCOUNT); - - viewModel.setAccount(account); - - View v = getActivity().getLayoutInflater().inflate(R.layout.edit_feed_layout, null); - - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) - .setTitle(R.string.edit_feed) - .setPositiveButton(R.string.validate, (dialog, which) -> { - Feed feed = feedWithFolder.getFeed(); - feed.setName(feedName.getText().toString().trim()); - feed.setUrl(feedUrl.getText().toString().trim()); - - viewModel.updateFeedWithFolder(feed) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(); - }); - - builder.setView(v); - fillData(v); - - viewModel.getFolders().observe(this, folders -> { - values = new TreeMap<>(String::compareTo); - - if (!account.getAccountType().getAccountConfig().getAddNoFolder()) - values.put(getString(R.string.no_folder), 0); - - for (Folder folder : folders) { - values.put(folder.getName(), folder.getId()); - } - - ArrayAdapter adapter = new ArrayAdapter<>(getActivity(), - android.R.layout.simple_spinner_dropdown_item, new ArrayList<>(values.keySet())); - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - - folder.setAdapter(adapter); - folder.setOnItemSelectedListener(this); - - if (feedWithFolder.getFolder() != null) - folder.setSelection(adapter.getPosition(feedWithFolder.getFolder().getName())); - else - folder.setSelection(adapter.getPosition(getString(R.string.no_folder))); - }); - - return builder.create(); - } - - private void fillData(View v) { - feedName = v.findViewById(R.id.edit_feed_name_edit_text); - feedUrl = v.findViewById(R.id.edit_feed_url_edit_text); - folder = v.findViewById(R.id.edit_feed_folder_spinner); - - if (!account.getAccountType().getAccountConfig().isFeedUrlReadOnly()) - feedUrl.setEnabled(false); - - feedName.setText(feedWithFolder.getFeed().getName()); - feedUrl.setText(feedWithFolder.getFeed().getUrl()); - } - - @Override - public void onItemSelected(AdapterView parent, View view, int position, long id) { - String folderName = (String) parent.getAdapter().getItem(position); - int folderId = values.get(folderName); - - feedWithFolder.getFeed().setFolderId(folderId == 0 ? null : folderId); - } - - @Override - public void onNothingSelected(AdapterView parent) { - - } -} diff --git a/app/src/main/java/com/readrops/app/feedsfolders/feeds/FeedOptionsDialogFragment.kt b/app/src/main/java/com/readrops/app/feedsfolders/feeds/FeedOptionsDialogFragment.kt deleted file mode 100644 index eea856e6..00000000 --- a/app/src/main/java/com/readrops/app/feedsfolders/feeds/FeedOptionsDialogFragment.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.readrops.app.feedsfolders.feeds - -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.readrops.app.databinding.FeedOptionsLayoutBinding -import com.readrops.app.utils.ReadropsKeys.ACCOUNT -import com.readrops.db.entities.account.Account -import com.readrops.db.pojo.FeedWithFolder - -class FeedOptionsDialogFragment : BottomSheetDialogFragment() { - - private lateinit var feedWithFolder: FeedWithFolder - private lateinit var account: Account - - private var _binding: FeedOptionsLayoutBinding? = null - private val binding get() = _binding!! - - companion object { - const val FEED_KEY = "FEED_KEY" - - fun newInstance(feedWithFolder: FeedWithFolder, account: Account): FeedOptionsDialogFragment { - val bundle = Bundle() - bundle.putParcelable(FEED_KEY, feedWithFolder) - bundle.putParcelable(ACCOUNT, account) - - val feedsOptionsDialogFragment = FeedOptionsDialogFragment() - feedsOptionsDialogFragment.arguments = bundle - - return feedsOptionsDialogFragment - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - feedWithFolder = arguments?.getParcelable(FEED_KEY)!! - account = arguments?.getParcelable(ACCOUNT)!! - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - _binding = FeedOptionsLayoutBinding.inflate(inflater, container, false) - - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.feedOptionsTitle.text = feedWithFolder.feed.name - - binding.feedOptionsEditLayout.setOnClickListener { openEditFeedDialog() } - binding.feedOptionsOpenRootLayout.setOnClickListener { openFeedRootUrl() } - binding.feedOptionsDeleteLayout.setOnClickListener { deleteFeed() } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - private fun openEditFeedDialog() { - dismiss() - val editFeedDialogFragment = EditFeedDialogFragment.newInstance(feedWithFolder, account) - - activity - ?.supportFragmentManager - ?.beginTransaction() - ?.add(editFeedDialogFragment, "") - ?.commit() - } - - private fun openFeedRootUrl() { - dismiss() - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(feedWithFolder.feed.siteUrl))) - } - - private fun deleteFeed() { - dismiss() - (parentFragment as FeedsFragment).deleteFeed(feedWithFolder.feed) - } - -} diff --git a/app/src/main/java/com/readrops/app/feedsfolders/feeds/FeedsAdapter.java b/app/src/main/java/com/readrops/app/feedsfolders/feeds/FeedsAdapter.java deleted file mode 100644 index 597a7c8f..00000000 --- a/app/src/main/java/com/readrops/app/feedsfolders/feeds/FeedsAdapter.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.readrops.app.feedsfolders.feeds; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.ListAdapter; -import androidx.recyclerview.widget.RecyclerView; - -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.readrops.app.R; -import com.readrops.app.databinding.FeedLayoutBinding; -import com.readrops.app.utils.GlideRequests; -import com.readrops.db.pojo.FeedWithFolder; - -import org.koin.java.KoinJavaComponent; - -import java.util.List; - -public class FeedsAdapter extends ListAdapter { - - private ManageFeedsListener listener; - - private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { - @Override - public boolean areItemsTheSame(@NonNull FeedWithFolder feedWithFolder, @NonNull FeedWithFolder t1) { - return feedWithFolder.getFeed().getId() == t1.getFeed().getId(); - } - - @Override - public boolean areContentsTheSame(@NonNull FeedWithFolder feedWithFolder, @NonNull FeedWithFolder t1) { - boolean folder = false; - if (feedWithFolder.getFolder() != null && t1.getFolder() != null) - folder = feedWithFolder.getFolder().getName().equals(t1.getFolder().getName()); - - return feedWithFolder.getFeed().getName().equals(t1.getFeed().getName()) - && folder; - } - - @Nullable - @Override - public Object getChangePayload(@NonNull FeedWithFolder oldItem, @NonNull FeedWithFolder newItem) { - return newItem; - } - }; - - public FeedsAdapter(ManageFeedsListener listener) { - super(DIFF_CALLBACK); - - this.listener = listener; - } - - @NonNull - @Override - public FeedViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { - FeedLayoutBinding binding = FeedLayoutBinding.inflate(LayoutInflater.from(viewGroup.getContext()), viewGroup, false); - - return new FeedViewHolder(binding); - } - - @Override - public void onBindViewHolder(@NonNull FeedViewHolder viewHolder, int i) { - FeedWithFolder feedWithFolder = getItem(i); - - if (feedWithFolder.getFeed().getIconUrl() != null) { - KoinJavaComponent.get(GlideRequests.class) - .load(feedWithFolder.getFeed().getIconUrl()) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .placeholder(R.drawable.ic_rss_feed_grey) - .into(viewHolder.binding.feedLayoutIcon); - } else - viewHolder.binding.feedLayoutIcon.setImageResource(R.drawable.ic_rss_feed_grey); - - viewHolder.binding.feedLayoutName.setText(feedWithFolder.getFeed().getName()); - if (feedWithFolder.getFeed().getDescription() != null) { - viewHolder.binding.feedLayoutDescription.setVisibility(View.VISIBLE); - viewHolder.binding.feedLayoutDescription.setText(feedWithFolder.getFeed().getDescription()); - } else - viewHolder.binding.feedLayoutDescription.setVisibility(View.GONE); - - if (feedWithFolder.getFolder() != null) - viewHolder.binding.feedLayoutFolder.setText(feedWithFolder.getFolder().getName()); - else - viewHolder.binding.feedLayoutFolder.setText(R.string.no_folder); - - viewHolder.itemView.setOnClickListener(v -> listener.onEdit(feedWithFolder)); - viewHolder.itemView.setOnLongClickListener(v -> { - listener.onOpenLink(feedWithFolder); - return true; - }); - } - - - @Override - public void onBindViewHolder(@NonNull FeedViewHolder holder, int position, @NonNull List payloads) { - if (!payloads.isEmpty()) { - FeedWithFolder feedWithFolder = (FeedWithFolder) payloads.get(0); - - holder.binding.feedLayoutName.setText(feedWithFolder.getFeed().getName()); - - if (feedWithFolder.getFolder() != null) - holder.binding.feedLayoutName.setText(feedWithFolder.getFolder().getName()); - else - holder.binding.feedLayoutName.setText(R.string.no_folder); - - } else - onBindViewHolder(holder, position); - } - - public interface ManageFeedsListener { - void onOpenLink(FeedWithFolder feedWithFolder); - - void onEdit(FeedWithFolder feedWithFolder); - } - - - protected class FeedViewHolder extends RecyclerView.ViewHolder { - - private FeedLayoutBinding binding; - - public FeedViewHolder(FeedLayoutBinding binding) { - super(binding.getRoot()); - - this.binding = binding; - } - } -} diff --git a/app/src/main/java/com/readrops/app/feedsfolders/feeds/FeedsFragment.java b/app/src/main/java/com/readrops/app/feedsfolders/feeds/FeedsFragment.java deleted file mode 100644 index 34b6efb6..00000000 --- a/app/src/main/java/com/readrops/app/feedsfolders/feeds/FeedsFragment.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.readrops.app.feedsfolders.feeds; - - -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT; - -import android.content.res.Resources; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.LinearLayoutManager; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.readrops.app.R; -import com.readrops.app.databinding.FragmentFeedsBinding; -import com.readrops.app.feedsfolders.ManageFeedsFoldersViewModel; -import com.readrops.app.utils.SharedPreferencesManager; -import com.readrops.app.utils.Utils; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.account.Account; -import com.readrops.db.pojo.FeedWithFolder; - -import org.koin.android.compat.SharedViewModelCompat; - -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.observers.DisposableCompletableObserver; -import io.reactivex.schedulers.Schedulers; - - -public class FeedsFragment extends Fragment { - - private FeedsAdapter adapter; - private ManageFeedsFoldersViewModel viewModel; - private Account account; - - private FragmentFeedsBinding binding; - - public FeedsFragment() { - // Required empty public constructor - } - - public static FeedsFragment newInstance(Account account) { - FeedsFragment fragment = new FeedsFragment(); - Bundle args = new Bundle(); - - args.putParcelable(ACCOUNT, account); - fragment.setArguments(args); - - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - account = getArguments().getParcelable(ACCOUNT); - - if (account.getLogin() == null) - account.setLogin(SharedPreferencesManager.readString(account.getLoginKey())); - if (account.getPassword() == null) - account.setPassword(SharedPreferencesManager.readString(account.getPasswordKey())); - - viewModel = SharedViewModelCompat.sharedViewModel(this, ManageFeedsFoldersViewModel.class).getValue(); - viewModel.setAccount(account); - - viewModel.getFeedsWithFolder().observe(this, feedWithFolders -> { - adapter.submitList(feedWithFolders); - - if (feedWithFolders.size() > 0) { - binding.feedsEmptyList.setVisibility(View.GONE); - } else { - binding.feedsEmptyList.setVisibility(View.VISIBLE); - } - }); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - binding = FragmentFeedsBinding.inflate(inflater, container, false); - - return binding.getRoot(); - } - - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - binding.feedsRecyclerview.setLayoutManager(new LinearLayoutManager(getActivity())); - - adapter = new FeedsAdapter(new FeedsAdapter.ManageFeedsListener() { - @Override - public void onEdit(FeedWithFolder feedWithFolder) { - openFeedOptionsFragment(feedWithFolder); - } - - @Override - public void onOpenLink(FeedWithFolder feedWithFolder) { - } - }); - - binding.feedsRecyclerview.setAdapter(adapter); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - binding = null; - } - - public void deleteFeed(Feed feed) { - new MaterialDialog.Builder(getContext()) - .title(R.string.delete_feed) - .positiveText(R.string.validate) - .negativeText(R.string.cancel) - .onPositive((dialog, which) -> viewModel.deleteFeed(feed) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new DisposableCompletableObserver() { - @Override - public void onComplete() { - Utils.showSnackbar(binding.feedsRoot, - getString(R.string.feed_deleted, feed.getName())); - } - - @Override - public void onError(Throwable e) { - String message; - if (e instanceof Resources.NotFoundException) - message = getString(R.string.feed_doesnt_exist, feed.getName()); - else - message = getString(R.string.error_occured); - - Utils.showSnackbar(binding.feedsRoot, message); - } - })) - .show(); - } - - private void openFeedOptionsFragment(FeedWithFolder feedWithFolder) { - FeedOptionsDialogFragment dialogFragment = FeedOptionsDialogFragment.Companion.newInstance(feedWithFolder, account); - - getChildFragmentManager() - .beginTransaction() - .add(dialogFragment, "") - .commit(); - } -} diff --git a/app/src/main/java/com/readrops/app/feedsfolders/folders/FolderOptionsDialogFragment.kt b/app/src/main/java/com/readrops/app/feedsfolders/folders/FolderOptionsDialogFragment.kt deleted file mode 100644 index 853e6ce5..00000000 --- a/app/src/main/java/com/readrops/app/feedsfolders/folders/FolderOptionsDialogFragment.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.readrops.app.feedsfolders.folders - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.readrops.app.databinding.FolderOptionsLayoutBinding -import com.readrops.db.entities.Folder - -class FolderOptionsDialogFragment : BottomSheetDialogFragment() { - - private lateinit var folder: Folder - - private var _binding: FolderOptionsLayoutBinding? = null - private val binding get() = _binding!! - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - folder = arguments?.getParcelable(FOLDER_KEY)!! - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - _binding = FolderOptionsLayoutBinding.inflate(inflater, container, false) - - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.folderOptionsTitle.text = folder.name - binding.folderOptionsEdit.setOnClickListener { openEditFolderDialog() } - binding.folderOptionsDelete.setOnClickListener { deleteFolder() } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - private fun openEditFolderDialog() { - dismiss() - (parentFragment as FoldersFragment).editFolder(folder) - } - - private fun deleteFolder() { - dismiss() - (parentFragment as FoldersFragment).deleteFolder(folder) - } - - companion object { - const val FOLDER_KEY = "FOLDER_KEY" - - fun newInstance(folder: Folder): FolderOptionsDialogFragment { - val args = Bundle() - args.putParcelable(FOLDER_KEY, folder) - - val fragment = FolderOptionsDialogFragment() - fragment.arguments = args - - return fragment - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/feedsfolders/folders/FoldersAdapter.java b/app/src/main/java/com/readrops/app/feedsfolders/folders/FoldersAdapter.java deleted file mode 100644 index 85266e92..00000000 --- a/app/src/main/java/com/readrops/app/feedsfolders/folders/FoldersAdapter.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.readrops.app.feedsfolders.folders; - -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.ListAdapter; -import androidx.recyclerview.widget.RecyclerView; - -import com.readrops.app.R; -import com.readrops.app.databinding.FolderLayoutBinding; -import com.readrops.db.entities.Folder; -import com.readrops.db.pojo.FolderWithFeedCount; - -import java.util.List; - -public class FoldersAdapter extends ListAdapter { - - private ManageFoldersListener listener; - private int totalFeedCount; - - public FoldersAdapter(ManageFoldersListener listener) { - super(DIFF_CALLBACK); - - this.listener = listener; - } - - public void setTotalFeedCount(int totalFeedCount) { - this.totalFeedCount = totalFeedCount; - } - - private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { - @Override - public boolean areItemsTheSame(@NonNull FolderWithFeedCount oldItem, @NonNull FolderWithFeedCount newItem) { - return oldItem.getFolder().getId() == newItem.getFolder().getId(); - } - - @Override - public boolean areContentsTheSame(@NonNull FolderWithFeedCount oldItem, @NonNull FolderWithFeedCount newItem) { - return TextUtils.equals(oldItem.getFolder().getName(), newItem.getFolder().getName()) && - oldItem.getFeedCount() == newItem.getFeedCount(); - } - - @Nullable - @Override - public Object getChangePayload(@NonNull FolderWithFeedCount oldItem, @NonNull FolderWithFeedCount newItem) { - return newItem; - } - }; - - @NonNull - @Override - public FolderViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - FolderLayoutBinding binding = FolderLayoutBinding.inflate(LayoutInflater.from(parent.getContext()), - parent, false); - - return new FolderViewHolder(binding); - } - - @Override - public void onBindViewHolder(@NonNull FolderViewHolder holder, int position, @NonNull List payloads) { - if (!payloads.isEmpty()) { - FolderWithFeedCount folderWithFeedCount = (FolderWithFeedCount) payloads.get(0); - - holder.bind(folderWithFeedCount); - } else - onBindViewHolder(holder, position); - - } - - @Override - public void onBindViewHolder(@NonNull FolderViewHolder holder, int position) { - FolderWithFeedCount folderWithFeedCount = getItem(position); - - holder.bind(folderWithFeedCount); - holder.itemView.setOnClickListener(v -> listener.onClick(folderWithFeedCount.getFolder())); - } - - public interface ManageFoldersListener { - void onClick(Folder folder); - } - - public class FolderViewHolder extends RecyclerView.ViewHolder { - - private FolderLayoutBinding binding; - - public FolderViewHolder(FolderLayoutBinding binding) { - super(binding.getRoot()); - - this.binding = binding; - } - - private void bind(FolderWithFeedCount folderWithFeedCount) { - binding.folderName.setText(folderWithFeedCount.getFolder().getName()); - - int stringRes = folderWithFeedCount.getFeedCount() > 1 ? R.string.feeds_number : R.string.feed_number; - binding.folderFeedsCount.setText(itemView.getContext().getString(stringRes, String.valueOf(folderWithFeedCount.getFeedCount()))); - - binding.folderProgressBar.setMax(totalFeedCount); - binding.folderProgressBar.setProgress(folderWithFeedCount.getFeedCount()); - } - } -} diff --git a/app/src/main/java/com/readrops/app/feedsfolders/folders/FoldersFragment.java b/app/src/main/java/com/readrops/app/feedsfolders/folders/FoldersFragment.java deleted file mode 100644 index 3881e4c9..00000000 --- a/app/src/main/java/com/readrops/app/feedsfolders/folders/FoldersFragment.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.readrops.app.feedsfolders.folders; - - -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT; - -import android.content.res.Resources; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.LinearLayoutManager; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.readrops.api.utils.exceptions.ConflictException; -import com.readrops.api.utils.exceptions.UnknownFormatException; -import com.readrops.app.R; -import com.readrops.app.databinding.FragmentFoldersBinding; -import com.readrops.app.feedsfolders.ManageFeedsFoldersViewModel; -import com.readrops.app.utils.SharedPreferencesManager; -import com.readrops.app.utils.Utils; -import com.readrops.db.entities.Folder; -import com.readrops.db.entities.account.Account; - -import org.koin.android.compat.SharedViewModelCompat; - -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.observers.DisposableSingleObserver; -import io.reactivex.schedulers.Schedulers; - -public class FoldersFragment extends Fragment { - - private FoldersAdapter adapter; - private FragmentFoldersBinding binding; - private ManageFeedsFoldersViewModel viewModel; - - private Account account; - - public FoldersFragment() { - // Required empty public constructor - } - - public static FoldersFragment newInstance(Account account) { - FoldersFragment fragment = new FoldersFragment(); - - Bundle args = new Bundle(); - args.putParcelable(ACCOUNT, account); - fragment.setArguments(args); - - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - account = getArguments().getParcelable(ACCOUNT); - - if (account.getLogin() == null) - account.setLogin(SharedPreferencesManager.readString(account.getLoginKey())); - if (account.getPassword() == null) - account.setPassword(SharedPreferencesManager.readString(account.getPasswordKey())); - - adapter = new FoldersAdapter(this::openFolderOptionsDialog); - viewModel = SharedViewModelCompat.sharedViewModel(this, ManageFeedsFoldersViewModel.class).getValue(); - - viewModel.setAccount(account); - viewModel.getFeedCountByAccount() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new DisposableSingleObserver() { - @Override - public void onSuccess(Integer feedCount) { - adapter.setTotalFeedCount(feedCount); - getFoldersWithFeedCount(); - } - - @Override - public void onError(Throwable e) { - Utils.showSnackbar(binding.foldersRoot, e.getMessage()); - } - }); - } - - private void getFoldersWithFeedCount() { - viewModel.getFoldersWithFeedCount().observe(this, folders -> { - adapter.submitList(folders); - - if (!folders.isEmpty()) { - binding.foldersEmptyList.setVisibility(View.GONE); - } else { - binding.foldersEmptyList.setVisibility(View.VISIBLE); - } - }); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - binding = FragmentFoldersBinding.inflate(inflater, container, false); - - return binding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - binding.foldersList.setLayoutManager(new LinearLayoutManager(getContext())); - binding.foldersList.setAdapter(adapter); - } - - public void editFolder(Folder folder) { - new MaterialDialog.Builder(getActivity()) - .title(R.string.edit_folder) - .positiveText(R.string.validate) - .input(getString(R.string.folder), folder.getName(), false, (dialog, input) -> { - folder.setName(input.toString()); - - viewModel.updateFolder(folder) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(throwable -> { - String message; - if (throwable instanceof ConflictException) - message = getString(R.string.folder_already_exists); - else if (throwable instanceof UnknownFormatException) - message = getString(R.string.folder_bad_format); - else if (throwable instanceof Resources.NotFoundException) - message = getString(R.string.folder_doesnt_exist); - else - message = getString(R.string.error_occured); - - Utils.showSnackbar(binding.foldersRoot, message); - }) - .subscribe(); - }) - .show(); - } - - public void deleteFolder(Folder folder) { - new MaterialDialog.Builder(getActivity()) - .title(R.string.delete_folder) - .negativeText(R.string.cancel) - .positiveText(R.string.validate) - .onPositive((dialog, which) -> viewModel.deleteFolder(folder) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(throwable -> { - String message; - if (throwable instanceof Resources.NotFoundException) - message = getString(R.string.folder_doesnt_exist); - else - message = throwable.getMessage(); - - Utils.showSnackbar(binding.foldersRoot, message); - }) - .subscribe()) - .show(); - } - - private void openFolderOptionsDialog(Folder folder) { - FolderOptionsDialogFragment fragment = FolderOptionsDialogFragment.Companion.newInstance(folder); - - getChildFragmentManager() - .beginTransaction() - .add(fragment, "") - .commit(); - } -} - diff --git a/app/src/main/java/com/readrops/app/item/ItemActivity.java b/app/src/main/java/com/readrops/app/item/ItemActivity.java deleted file mode 100644 index de201cab..00000000 --- a/app/src/main/java/com/readrops/app/item/ItemActivity.java +++ /dev/null @@ -1,429 +0,0 @@ -package com.readrops.app.item; - -import android.Manifest; -import android.app.DownloadManager; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.ColorStateList; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.os.Environment; -import android.provider.Settings; -import android.util.Log; -import android.view.ContextMenu; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.webkit.WebView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.browser.customtabs.CustomTabsIntent; -import androidx.core.app.ActivityCompat; -import androidx.core.app.ShareCompat; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.request.target.CustomTarget; -import com.bumptech.glide.request.transition.Transition; -import com.readrops.api.utils.DateUtils; -import com.readrops.app.R; -import com.readrops.app.databinding.ActivityItemBinding; -import com.readrops.app.utils.GlideRequests; -import com.readrops.app.utils.PermissionManager; -import com.readrops.app.utils.SharedPreferencesManager; -import com.readrops.app.utils.Utils; -import com.readrops.db.entities.Item; -import com.readrops.db.entities.account.Account; -import com.readrops.db.pojo.ItemWithFeed; - -import org.koin.android.compat.ViewModelCompat; -import org.koin.java.KoinJavaComponent; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; - -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT; -import static com.readrops.app.utils.ReadropsKeys.ACTION_BAR_COLOR; -import static com.readrops.app.utils.ReadropsKeys.IMAGE_URL; -import static com.readrops.app.utils.ReadropsKeys.ITEM_ID; -import static com.readrops.app.utils.ReadropsKeys.WEB_URL; - -public class ItemActivity extends AppCompatActivity { - - private static final String TAG = ItemActivity.class.getSimpleName(); - private static final int WRITE_EXTERNAL_STORAGE_REQUEST = 1; - - private ActivityItemBinding binding; - private ItemViewModel viewModel; - - private ItemWithFeed itemWithFeed; - - private boolean appBarCollapsed; - - private String urlToDownload; - private String imageTitle; - - private boolean uiBinded; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - binding = ActivityItemBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - - Intent intent = getIntent(); - int itemId = intent.getIntExtra(ITEM_ID, 0); - String imageUrl = intent.getStringExtra(IMAGE_URL); - Account account = intent.getParcelableExtra(ACCOUNT); - - setSupportActionBar(binding.collapsingLayoutToolbar); - - if (getSupportActionBar() != null) { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } - - registerForContextMenu(binding.itemWebview); - - if (imageUrl == null) { - getSupportActionBar().setDisplayShowTitleEnabled(false); - binding.collapsingLayout.setTitleEnabled(false); - binding.collapsingLayoutScrim.setVisibility(View.GONE); - } else { - binding.appBarLayout.setExpanded(true); - binding.collapsingLayout.setTitleEnabled(true); - - KoinJavaComponent.get(GlideRequests.class) - .load(imageUrl) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .into(binding.collapsingLayoutImage); - } - - final TypedArray styledAttributes = getTheme().obtainStyledAttributes( - new int[]{android.R.attr.actionBarSize}); - int actionBarSize = (int) styledAttributes.getDimension(0, 0); - styledAttributes.recycle(); - - binding.appBarLayout.addOnOffsetChangedListener(((appBarLayout1, i) -> { - appBarCollapsed = Math.abs(i) >= (binding.appBarLayout.getTotalScrollRange() - - actionBarSize - ((8 * binding.appBarLayout.getTotalScrollRange()) / 100)); - - invalidateOptionsMenu(); - })); - - viewModel = ViewModelCompat.getViewModel(this, ItemViewModel.class); - viewModel.setAccount(account); - viewModel.getItemById(itemId).observe(this, itemWithFeed1 -> { - if (!uiBinded) { - bindUI(itemWithFeed1); - uiBinded = true; - } - }); - - binding.activityItemFab.setOnClickListener(v -> openInNavigator()); - - binding.itemStarFab.setOnClickListener(v -> { - Item item = itemWithFeed.getItem(); - - if (item.isStarred()) { - binding.itemStarFab.setImageResource(R.drawable.ic_empty_star); - } else { - binding.itemStarFab.setImageResource(R.drawable.ic_star); - } - - item.setStarred(!item.isStarred()); - viewModel.setStarState(item) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(throwable -> Utils.showSnackbar(binding.itemRoot, throwable.getMessage())) - .subscribe(); - }); - - } - - private void bindUI(ItemWithFeed itemWithFeed) { - this.itemWithFeed = itemWithFeed; - Item item = itemWithFeed.getItem(); - - if (item.isStarred()) { - binding.itemStarFab.setImageResource(R.drawable.ic_star); - } - - binding.activityItemDate.setText(DateUtils.formattedDateTimeByLocal(item.getPubDate())); - - if (item.getImageLink() == null) - binding.collapsingLayoutToolbar.setTitle(itemWithFeed.getFeedName()); - else - binding.collapsingLayout.setTitle(itemWithFeed.getFeedName()); - - if (itemWithFeed.getFolder() != null) { - binding.collapsingLayoutToolbar.setSubtitle(itemWithFeed.getFolder().getName()); - } - - binding.activityItemTitle.setText(item.getTitle()); - - if (itemWithFeed.getBgColor() != 0) { - binding.activityItemTitle.setTextColor(itemWithFeed.getBgColor()); - Utils.setDrawableColor(binding.activityItemDateLayout.getBackground(), itemWithFeed.getBgColor()); - } else if (itemWithFeed.getColor() != 0) { - binding.activityItemTitle.setTextColor(itemWithFeed.getColor()); - Utils.setDrawableColor(binding.activityItemDateLayout.getBackground(), itemWithFeed.getColor()); - } - - if (item.getAuthor() != null && !item.getAuthor().isEmpty()) { - binding.activityItemAuthor.setText(getString(R.string.by_author, item.getAuthor())); - binding.activityItemAuthor.setVisibility(View.VISIBLE); - } - - if (item.getReadTime() > 0) { - int minutes = (int) Math.round(item.getReadTime()); - if (minutes < 1) - binding.activityItemReadtime.setText(getResources().getString(R.string.read_time_lower_than_1)); - else if (minutes > 1) - binding.activityItemReadtime.setText(getResources().getString(R.string.read_time, String.valueOf(minutes))); - else - binding.activityItemReadtime.setText(getResources().getString(R.string.read_time_one_minute)); - - binding.activityItemReadtimeLayout.setVisibility(View.VISIBLE); - } - - if (itemWithFeed.getBgColor() != 0) { - binding.collapsingLayout.setBackgroundColor(itemWithFeed.getBgColor()); - binding.collapsingLayout.setContentScrimColor(itemWithFeed.getBgColor()); - binding.collapsingLayout.setStatusBarScrimColor(itemWithFeed.getBgColor()); - - getWindow().setStatusBarColor(itemWithFeed.getBgColor()); - binding.activityItemFab.setBackgroundTintList(ColorStateList.valueOf(itemWithFeed.getBgColor())); - binding.itemStarFab.setBackgroundTintList(ColorStateList.valueOf(itemWithFeed.getBgColor())); - } else if (itemWithFeed.getColor() != 0) { - binding.collapsingLayout.setBackgroundColor(itemWithFeed.getColor()); - binding.collapsingLayout.setContentScrimColor(itemWithFeed.getColor()); - binding.collapsingLayout.setStatusBarScrimColor(itemWithFeed.getColor()); - - getWindow().setStatusBarColor(itemWithFeed.getColor()); - binding.activityItemFab.setBackgroundTintList(ColorStateList.valueOf(itemWithFeed.getColor())); - binding.itemStarFab.setBackgroundTintList(ColorStateList.valueOf(itemWithFeed.getColor())); - } - - binding.itemWebview.setItem(itemWithFeed); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.item_menu, menu); - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - MenuItem item = menu.findItem(R.id.item_open); - item.setVisible(appBarCollapsed); - - return super.onPrepareOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - case R.id.item_share: - shareArticle(); - return true; - case R.id.item_open: - openUrl(); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - @Override - public void onBackPressed() { - finish(); - super.onBackPressed(); - } - - private void openUrl() { - int value = Integer.parseInt(SharedPreferencesManager.readString( - SharedPreferencesManager.SharedPrefKey.OPEN_ITEMS_IN)); - switch (value) { - case 0: - openInNavigator(); - break; - case 1: - openInWebView(); - break; - default: - openInCustomTab(); - break; - } - } - - private void openInNavigator() { - Intent urlIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(itemWithFeed.getItem().getLink())); - startActivity(urlIntent); - } - - private void openInWebView() { - Intent intent = new Intent(this, WebViewActivity.class); - intent.putExtra(WEB_URL, itemWithFeed.getItem().getLink()); - intent.putExtra(ACTION_BAR_COLOR, itemWithFeed.getBgColor() != 0 ? itemWithFeed.getBgColor() : itemWithFeed.getColor()); - - startActivity(intent); - } - - private void openInCustomTab() { - boolean darkTheme = Boolean.parseBoolean(SharedPreferencesManager.readString(SharedPreferencesManager.SharedPrefKey.DARK_THEME)); - int color = itemWithFeed.getBgColor() != 0 ? itemWithFeed.getBgColor() : itemWithFeed.getColor(); - - CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder() - .addDefaultShareMenuItem() - .setToolbarColor(color) - .setSecondaryToolbarColor(color) - .setColorScheme(darkTheme ? CustomTabsIntent.COLOR_SCHEME_DARK : CustomTabsIntent.COLOR_SCHEME_LIGHT) - .enableUrlBarHiding() - .setShowTitle(true) - .build(); - - customTabsIntent.launchUrl(this, Uri.parse(itemWithFeed.getItem().getLink())); - } - - private void shareArticle() { - Intent shareIntent = new Intent(Intent.ACTION_SEND); - shareIntent.setType("text/plain"); - shareIntent.putExtra(Intent.EXTRA_TEXT, itemWithFeed.getItem().getTitle() + " - " + itemWithFeed.getItem().getLink()); - startActivity(Intent.createChooser(shareIntent, getString(R.string.share_article))); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - WebView.HitTestResult hitTestResult = binding.itemWebview.getHitTestResult(); - - if (hitTestResult.getType() == WebView.HitTestResult.IMAGE_TYPE || - hitTestResult.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { - new MaterialDialog.Builder(this) - .title(R.string.image_options) - .items(R.array.image_options) - .itemsCallback((dialog, itemView, position, text) -> { - switch (position) { - case 0: - shareImage(hitTestResult.getExtra()); - break; - case 1: - if (PermissionManager.isPermissionGranted(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) - downloadImage(hitTestResult.getExtra()); - else { - urlToDownload = hitTestResult.getExtra(); - PermissionManager.requestPermissions(this, WRITE_EXTERNAL_STORAGE_REQUEST, Manifest.permission.WRITE_EXTERNAL_STORAGE); - } - break; - case 2: - urlToDownload = hitTestResult.getExtra(); - String content = binding.itemWebview.getItemContent(); - - Pattern p = Pattern.compile("()"); - Matcher m = p.matcher(content); - if (m.matches()) { - Pattern p2 = Pattern.compile(""); - Matcher m2 = p2.matcher(content); - if (m2.matches()) { - imageTitle = m2.group(2); - } else { - imageTitle = ""; - } - } - new MaterialDialog.Builder(this) - .title(urlToDownload) - .content(imageTitle) - .show(); - break; - default: - throw new IllegalStateException("Unexpected value: " + position); - } - - }) - .show(); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - if (requestCode == WRITE_EXTERNAL_STORAGE_REQUEST) { - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - downloadImage(urlToDownload); - } else { - if (ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[0])) { - Utils.showSnackBarWithAction(binding.itemRoot, getString(R.string.download_image_permission), - getString(R.string.try_again), - v -> PermissionManager.requestPermissions(this, WRITE_EXTERNAL_STORAGE_REQUEST, - Manifest.permission.WRITE_EXTERNAL_STORAGE)); - } else { - Utils.showSnackBarWithAction(binding.itemRoot, getString(R.string.download_image_permission), - getString(R.string.permissions), v -> { - Intent intent = new Intent(); - intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - intent.setData(Uri.fromParts("package", getPackageName(), null)); - startActivity(intent); - }); - } - - } - } - - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - } - - private void downloadImage(String url) { - DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)) - .setTitle(getString(R.string.download_image)) - .setMimeType("image/png") - .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) - .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "image.png"); - - request.allowScanningByMediaScanner(); - - DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); - downloadManager.enqueue(request); - } - - private void shareImage(String url) { - KoinJavaComponent.get(GlideRequests.class) - .asBitmap() - .diskCacheStrategy(DiskCacheStrategy.ALL) - .load(url) - .into(new CustomTarget() { - @Override - public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition transition) { - try { - Uri uri = viewModel.saveImageInCache(resource, ItemActivity.this); - Intent intent = ShareCompat.IntentBuilder.from(ItemActivity.this) - .setType("image/png") - .setStream(uri) - .setChooserTitle(R.string.share_image) - .createChooserIntent() - .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - - startActivity(intent); - } catch (Exception e) { - Log.e(TAG, e.getMessage()); - } - } - - @Override - public void onLoadCleared(@Nullable Drawable placeholder) { - // not useful - } - }); - - } -} diff --git a/app/src/main/java/com/readrops/app/item/ItemViewModel.java b/app/src/main/java/com/readrops/app/item/ItemViewModel.java deleted file mode 100644 index 33127ea1..00000000 --- a/app/src/main/java/com/readrops/app/item/ItemViewModel.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.readrops.app.item; - -import android.content.Context; -import android.graphics.Bitmap; -import android.net.Uri; - -import androidx.annotation.NonNull; -import androidx.core.content.FileProvider; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.ViewModel; - -import com.readrops.app.repositories.ARepository; -import com.readrops.db.Database; -import com.readrops.db.entities.Item; -import com.readrops.db.entities.account.Account; -import com.readrops.db.pojo.ItemWithFeed; -import com.readrops.db.queries.ItemSelectionQueryBuilder; - -import org.koin.core.parameter.ParametersHolderKt; -import org.koin.java.KoinJavaComponent; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import io.reactivex.Completable; - -public class ItemViewModel extends ViewModel { - - private final Database database; - private Account account; - - public ItemViewModel(@NonNull Database database) { - this.database = database; - } - - public void setAccount(Account account) { - this.account = account; - } - - public LiveData getItemById(int id) { - return database.itemDao().getItemById(ItemSelectionQueryBuilder.buildQuery(id, - account.getConfig().getUseSeparateState())); - } - - public Completable setStarState(Item item) { - ARepository repository = KoinJavaComponent.get(ARepository.class, null, - () -> ParametersHolderKt.parametersOf(account)); - - return repository.setItemStarState(item); - } - - public Uri saveImageInCache(Bitmap bitmap, Context context) throws IOException { - File imagesFolder = new File(context.getCacheDir().getAbsolutePath(), "images"); - - if (!imagesFolder.exists()) - imagesFolder.mkdirs(); - - File image = new File(imagesFolder, "shared_image.png"); - OutputStream stream = new FileOutputStream(image); - bitmap.compress(Bitmap.CompressFormat.PNG, 90, stream); - - stream.flush(); - stream.close(); - - return FileProvider.getUriForFile(context, context.getPackageName(), image); - } -} diff --git a/app/src/main/java/com/readrops/app/item/WebViewActivity.kt b/app/src/main/java/com/readrops/app/item/WebViewActivity.kt deleted file mode 100644 index 8e5f0eab..00000000 --- a/app/src/main/java/com/readrops/app/item/WebViewActivity.kt +++ /dev/null @@ -1,133 +0,0 @@ -package com.readrops.app.item - -import android.annotation.SuppressLint -import android.content.Intent -import android.content.res.ColorStateList -import android.graphics.Bitmap -import android.graphics.drawable.ColorDrawable -import android.net.Uri -import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import android.view.View -import android.webkit.* -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.ContextCompat -import com.readrops.app.R -import com.readrops.app.databinding.ActivityWebViewBinding -import com.readrops.app.utils.ReadropsKeys -import com.readrops.app.utils.ReadropsKeys.ACTION_BAR_COLOR - -class WebViewActivity : AppCompatActivity() { - - private lateinit var binding: ActivityWebViewBinding - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityWebViewBinding.inflate(layoutInflater) - setContentView(binding.root) - - setSupportActionBar(binding.activityWebViewToolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - title = "" - - val actionBarColor = intent.getIntExtra(ACTION_BAR_COLOR, ContextCompat.getColor(this, R.color.colorPrimary)) - supportActionBar?.setBackgroundDrawable(ColorDrawable(actionBarColor)) - setWebViewSettings() - - with(binding) { - activityWebViewSwipe.setOnRefreshListener { binding.webView.reload() } - activityWebViewProgress.progressTintList = ColorStateList.valueOf(actionBarColor) - activityWebViewProgress.max = 100 - - val url: String = intent.getStringExtra(ReadropsKeys.WEB_URL)!! - webView.loadUrl(url) - } - - } - - @SuppressLint("SetJavaScriptEnabled") - fun setWebViewSettings() { - val settings: WebSettings = binding.webView.settings - - settings.javaScriptEnabled = true - settings.setSupportZoom(true) - - binding.webView.webViewClient = object : WebViewClient() { - override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { - binding.webView.loadUrl(request?.url.toString()) - return true - } - - override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { - with(binding) { - activityWebViewSwipe.isRefreshing = false - activityWebViewProgress.progress = 0 - activityWebViewProgress.visibility = View.VISIBLE - } - - super.onPageStarted(view, url, favicon) - } - } - - binding.webView.webChromeClient = object : WebChromeClient() { - override fun onReceivedTitle(view: WebView?, title: String?) { - setTitle(title) - supportActionBar?.subtitle = Uri.parse(view?.url).host - - super.onReceivedTitle(view, title) - } - - override fun onProgressChanged(view: WebView?, newProgress: Int) { - with(binding) { - activityWebViewProgress.progress = newProgress - if (newProgress == 100) activityWebViewProgress.visibility = View.GONE - } - - - super.onProgressChanged(view, newProgress) - } - } - } - - override fun onBackPressed() { - if (binding.webView.canGoBack()) - binding.webView.goBack() - else - super.onBackPressed() - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - if (binding.webView.canGoBack()) - binding.webView.goBack() - else - finish() - return true - } - R.id.web_view_refresh -> { - binding.webView.reload() - } - R.id.web_view_share -> { - shareLink() - } - } - - return super.onOptionsItemSelected(item) - } - - private fun shareLink() { - val intent = Intent(Intent.ACTION_SEND).apply { - type = "text/plain" - putExtra(Intent.EXTRA_TEXT, binding.webView.url.toString()) - } - - startActivity(Intent.createChooser(intent, getString(R.string.share_url))) - } - - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.webview_menu, menu) - return true - } -} diff --git a/app/src/main/java/com/readrops/app/itemslist/DrawerManager.java b/app/src/main/java/com/readrops/app/itemslist/DrawerManager.java deleted file mode 100644 index d64a24fb..00000000 --- a/app/src/main/java/com/readrops/app/itemslist/DrawerManager.java +++ /dev/null @@ -1,412 +0,0 @@ -package com.readrops.app.itemslist; - -import static com.readrops.app.utils.Utils.drawableWithColor; - -import android.app.Activity; -import android.graphics.drawable.Drawable; -import android.view.View; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.Toolbar; -import androidx.recyclerview.widget.RecyclerView; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.request.target.CustomTarget; -import com.bumptech.glide.request.transition.Transition; -import com.mikepenz.fastadapter.FastAdapter; -import com.mikepenz.fastadapter.expandable.ExpandableExtension; -import com.mikepenz.fastadapter.listeners.ClickEventHook; -import com.mikepenz.fastadapter.select.SelectExtension; -import com.mikepenz.materialdrawer.AccountHeader; -import com.mikepenz.materialdrawer.AccountHeaderBuilder; -import com.mikepenz.materialdrawer.Drawer; -import com.mikepenz.materialdrawer.DrawerBuilder; -import com.mikepenz.materialdrawer.holder.ImageHolder; -import com.mikepenz.materialdrawer.model.DividerDrawerItem; -import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; -import com.mikepenz.materialdrawer.model.ProfileDrawerItem; -import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem; -import com.mikepenz.materialdrawer.model.SecondaryDrawerItem; -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; -import com.mikepenz.materialdrawer.model.interfaces.IProfile; -import com.readrops.app.R; -import com.readrops.app.utils.SharedPreferencesManager; -import com.readrops.app.utils.customviews.CustomExpandableBadgeDrawerItem; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.Folder; -import com.readrops.db.entities.account.Account; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class DrawerManager { - - public static final int ARTICLES_ITEM_ID = -5; - public static final int READ_LATER_ID = -6; - public static final int STARS_ID = -10; - public static final int ADD_ACCOUNT_ID = -4; - public static final int ABOUT_ID = -7; - public static final int SETTINGS_ID = -8; - public static final int ACCOUNT_SETTINGS_ID = -9; - - private Activity activity; - private Toolbar toolbar; - private Drawer drawer; - private FastAdapter adapter; - - private AccountHeader header; - private Drawer.OnDrawerItemClickListener listener; - private AccountHeader.OnAccountHeaderListener headerListener; - - public DrawerManager(Activity activity, Toolbar toolbar, Drawer.OnDrawerItemClickListener listener) { - this.activity = activity; - this.listener = listener; - this.toolbar = toolbar; - } - - public void setHeaderListener(AccountHeader.OnAccountHeaderListener headerListener) { - this.headerListener = headerListener; - } - - public Drawer buildDrawer(List accounts, int currentAccountId) { - createAccountHeader(accounts, currentAccountId); - - drawer = new DrawerBuilder() - .withActivity(activity) - .withToolbar(toolbar) - .withAccountHeader(header) - .withSelectedItem(DrawerManager.ARTICLES_ITEM_ID) - .withOnDrawerItemClickListener(listener) - .build(); - - adapter = drawer.getAdapter(); - buildFastAdapter(); - - addDefaultPlaces(); - - return drawer; - } - - public void buildFastAdapter() { - // Folder click - adapter.withEventHook(new ClickEventHook() { - @Override - public void onClick(@NonNull View v, int position, @NonNull FastAdapter fastAdapter, @NonNull IDrawerItem item) { - SelectExtension selectExtension = adapter.getExtension(SelectExtension.class); - - selectExtension.deselect(selectExtension.getSelections()); - - if (!item.isSelected()) { - selectExtension.select(position); - } - - listener.onItemClick(v, position, item); - } - - @Override - public List onBindMany(@NonNull RecyclerView.ViewHolder viewHolder) { - if (viewHolder instanceof CustomExpandableBadgeDrawerItem.ViewHolder) { - CustomExpandableBadgeDrawerItem.ViewHolder expandableViewHolder = (CustomExpandableBadgeDrawerItem.ViewHolder) viewHolder; - - return Arrays.asList(new View[]{ - expandableViewHolder.itemView.findViewById(R.id.expandable_item_container), - expandableViewHolder.itemView.findViewById(R.id.material_drawer_icon), - expandableViewHolder.itemView.findViewById(R.id.material_drawer_name), - expandableViewHolder.itemView.findViewById(R.id.material_drawer_description) - }.clone()); - - } else { - return Collections.emptyList(); - } - } - }); - - // Expandable click - adapter.withEventHook(new ClickEventHook() { - @Override - public void onClick(@NonNull View v, int position, @NonNull FastAdapter fastAdapter, @NonNull IDrawerItem item) { - ExpandableExtension expandableExtension = adapter.getExtension(ExpandableExtension.class); - - expandableExtension.toggleExpandable(position); - } - - @Override - public List onBindMany(@NonNull RecyclerView.ViewHolder viewHolder) { - if (viewHolder instanceof CustomExpandableBadgeDrawerItem.ViewHolder) { - CustomExpandableBadgeDrawerItem.ViewHolder expandableViewHolder = (CustomExpandableBadgeDrawerItem.ViewHolder) viewHolder; - - return Arrays.asList(new View[]{ - expandableViewHolder.badge, - expandableViewHolder.badgeContainer, - expandableViewHolder.arrow, - expandableViewHolder.itemView.findViewById(R.id.material_drawer_arrow_container) - }.clone()); - - } else { - return Collections.emptyList(); - } - } - }); - } - - public void updateDrawer(Map> folderListMap) { - drawer.removeAllItems(); - drawer.removeAllStickyFooterItems(); - - addDefaultPlaces(); - - Map feedsWithoutFolder = new HashMap<>(); - boolean hideFeeds = SharedPreferencesManager - .readBoolean(SharedPreferencesManager.SharedPrefKey.HIDE_FEEDS); - - for (Map.Entry> entry : folderListMap.entrySet()) { - Folder folder = entry.getKey(); - if (folder != null) { - CustomExpandableBadgeDrawerItem badgeDrawerItem = new CustomExpandableBadgeDrawerItem() - .withIdentifier(folder.getId() * 1000L) // to avoid any id conflict with other items - .withName(folder.getName()) - .withIcon(R.drawable.ic_folder_grey); - - List secondaryDrawerItems = new ArrayList<>(); - int expandableUnreadCount = 0; - - for (Feed feed : entry.getValue()) { - expandableUnreadCount += feed.getUnreadCount(); - - SecondaryDrawerItem secondaryDrawerItem = createSecondaryItem(feed); - - if (hideFeeds) { - if (feed.getUnreadCount() > 0) { - secondaryDrawerItems.add(secondaryDrawerItem); - } - } else { - secondaryDrawerItems.add(secondaryDrawerItem); - } - - loadItemIcon(secondaryDrawerItem, feed); - } - - boolean showItem; - if (hideFeeds) { - showItem = expandableUnreadCount > 0; - } else { - showItem = true; - } - - if (!secondaryDrawerItems.isEmpty() && showItem) { - badgeDrawerItem.withSubItems(secondaryDrawerItems); - badgeDrawerItem.withBadge(String.valueOf(expandableUnreadCount)); - drawer.addItem(badgeDrawerItem); - } - } else { // no folder case, items to add after the folders - for (Feed feed : folderListMap.get(folder)) { - SecondaryDrawerItem secondaryItem = createSecondaryItem(feed); - feedsWithoutFolder.put(secondaryItem, feed); - } - } - } - - // work-around as MaterialDrawer doesn't accept an item list - for (Map.Entry entry : feedsWithoutFolder.entrySet()) { - drawer.addItem(entry.getKey()); - loadItemIcon(entry.getKey(), entry.getValue()); - } - } - - private void createAccountHeader(List accounts, int currentAccountId) { - ProfileDrawerItem[] profileItems = new ProfileDrawerItem[accounts.size()]; - - for (int i = 0; i < accounts.size(); i++) { - Account account = accounts.get(i); - - // if currentAccount > 0, it means that the current account is no longer - if (account.isCurrentAccount() && currentAccountId == 0) - currentAccountId = account.getId(); - - ProfileDrawerItem profileItem = createProfileItem(account); - profileItems[i] = profileItem; - } - - header = new AccountHeaderBuilder() - .withActivity(activity) - .addProfiles(profileItems) - .withDividerBelowHeader(false) - .withAlternativeProfileHeaderSwitching(true) - .withCurrentProfileHiddenInList(true) - .withTextColorRes(R.color.colorBackground) - .withHeaderBackground(R.drawable.header_background) - .withHeaderBackgroundScaleType(ImageView.ScaleType.CENTER_CROP) - .withOnAccountHeaderListener(headerListener) - .build(); - - addProfileSettingItems(); - - header.setActiveProfile(currentAccountId); - } - - private ProfileDrawerItem createProfileItem(Account account) { - return new ProfileDrawerItem() - .withIcon(account.getAccountType().getIconRes()) - .withName(account.getDisplayedName()) - .withEmail(account.getAccountName()) - .withIdentifier(account.getId()); - } - - private SecondaryDrawerItem createSecondaryItem(Feed feed) { - int color = feed.getTextColor(); - - return new SecondaryDrawerItem() - .withName(feed.getName()) - .withBadge(String.valueOf(feed.getUnreadCount())) - .withIcon(color != 0 ? drawableWithColor(color) : drawableWithColor(activity.getResources().getColor(R.color.colorPrimary))) - .withIdentifier(feed.getId()); - } - - private void loadItemIcon(SecondaryDrawerItem secondaryDrawerItem, Feed feed) { - Glide.with(activity) - .load(feed.getIconUrl()) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .into(new CustomTarget() { - @Override - public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { - drawer.updateIcon(secondaryDrawerItem.getIdentifier(), new ImageHolder(resource)); - } - - @Override - public void onLoadCleared(@Nullable Drawable placeholder) { - // no need of this method - } - }); - } - - private void addDefaultPlaces() { - PrimaryDrawerItem articles = new PrimaryDrawerItem() - .withName(R.string.articles) - .withIcon(R.drawable.ic_rss_feed_grey) - .withSelectable(true) - .withIdentifier(ARTICLES_ITEM_ID); - - PrimaryDrawerItem toReadLater = new PrimaryDrawerItem() - .withName(R.string.read_later) - .withIcon(R.drawable.ic_read_later) - .withSelectable(true) - .withIdentifier(READ_LATER_ID); - - PrimaryDrawerItem favorites = new PrimaryDrawerItem() - .withName(R.string.favorites) - .withIcon(R.drawable.ic_star) - .withSelectable(true) - .withIdentifier(STARS_ID); - - PrimaryDrawerItem aboutItem = new PrimaryDrawerItem() - .withName(R.string.about) - .withIcon(R.drawable.ic_about_grey) - .withSelectable(false) - .withIdentifier(ABOUT_ID); - - PrimaryDrawerItem settingsItem = new PrimaryDrawerItem() - .withName(R.string.settings) - .withIcon(R.drawable.ic_settings) - .withSelectable(false) - .withIdentifier(SETTINGS_ID); - - drawer.addStickyFooterItem(settingsItem); - drawer.addStickyFooterItem(aboutItem); - - drawer.addItem(articles); - drawer.addItem(favorites); - drawer.addItem(toReadLater); - drawer.addItem(new DividerDrawerItem()); - } - - private void addProfileSettingItems() { - ProfileSettingDrawerItem accountSettingsItem = new ProfileSettingDrawerItem() - .withName(R.string.account_settings) - .withIcon(R.drawable.ic_settings) - .withIdentifier(ACCOUNT_SETTINGS_ID); - - ProfileSettingDrawerItem addAccountSettingsItem = new ProfileSettingDrawerItem() - .withName(R.string.add_account) - .withIcon(R.drawable.ic_add_account_grey) - .withIdentifier(ADD_ACCOUNT_ID); - - header.addProfiles(accountSettingsItem, addAccountSettingsItem); - } - - public void addAccount(Account account, boolean currentProfile) { - ProfileDrawerItem profileItem = createProfileItem(account); - - header.addProfiles(profileItem); - - if (currentProfile) - header.setActiveProfile(profileItem.getIdentifier()); - } - - public void setAccount(int accountId) { - header.setActiveProfile(accountId); - } - - public void updateHeader(List accounts) { - header.clear(); - addProfileSettingItems(); - - for (Account account : accounts) { - addAccount(account, account.isCurrentAccount()); - } - } - - public int getNumberOfProfiles() { - List profiles = header.getProfiles(); - - int number = 0; - for (IProfile profile : profiles) { - if (profile instanceof ProfileDrawerItem) - number++; - } - - return number; - } - - public void resetItems() { - drawer.removeAllItems(); - drawer.removeAllStickyFooterItems(); - addDefaultPlaces(); - } - - public void disableAccountSelection() { - List profiles = header.getProfiles(); - - for (IProfile profile : profiles) { - if (profile.getIdentifier() != header.getActiveProfile().getIdentifier() && !(profile instanceof ProfileSettingDrawerItem)) { - profile.withSelectable(false); - header.updateProfile(profile); - } - } - } - - public void enableAccountSelection() { - List profiles = header.getProfiles(); - - for (IProfile profile : profiles) { - if (profile.getIdentifier() != header.getActiveProfile().getIdentifier() && !(profile instanceof ProfileSettingDrawerItem)) { - profile.withSelectable(true); - header.updateProfile(profile); - } - } - } - - public void setDrawerSelection(long identifier) { - drawer.setSelection(identifier); - } - - public long getCurrentSelection() { - return drawer.getCurrentSelection(); - } -} diff --git a/app/src/main/java/com/readrops/app/itemslist/MainActivity.java b/app/src/main/java/com/readrops/app/itemslist/MainActivity.java deleted file mode 100644 index 24f14206..00000000 --- a/app/src/main/java/com/readrops/app/itemslist/MainActivity.java +++ /dev/null @@ -1,868 +0,0 @@ -package com.readrops.app.itemslist; - -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT; -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT_ID; -import static com.readrops.app.utils.ReadropsKeys.FEEDS; -import static com.readrops.app.utils.ReadropsKeys.FROM_MAIN_ACTIVITY; -import static com.readrops.app.utils.ReadropsKeys.IMAGE_URL; -import static com.readrops.app.utils.ReadropsKeys.ITEM_ID; -import static com.readrops.app.utils.ReadropsKeys.SETTINGS; -import static com.readrops.app.utils.ReadropsKeys.SYNCING; - -import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.util.Log; -import android.view.ActionMode; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; -import androidx.core.graphics.drawable.DrawableCompat; -import androidx.drawerlayout.widget.DrawerLayout; -import androidx.paging.PagedList; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.ItemTouchHelper; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.bumptech.glide.Glide; -import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader; -import com.bumptech.glide.util.ViewPreloadSizeProvider; -import com.mikepenz.aboutlibraries.Libs; -import com.mikepenz.aboutlibraries.LibsBuilder; -import com.mikepenz.aboutlibraries.LibsConfiguration; -import com.mikepenz.aboutlibraries.entity.Library; -import com.mikepenz.materialdrawer.Drawer; -import com.mikepenz.materialdrawer.model.PrimaryDrawerItem; -import com.mikepenz.materialdrawer.model.SecondaryDrawerItem; -import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; -import com.readrops.app.R; -import com.readrops.app.account.AccountTypeListActivity; -import com.readrops.app.account.AccountViewModel; -import com.readrops.app.addfeed.AddFeedActivity; -import com.readrops.app.databinding.ActivityMainBinding; -import com.readrops.app.item.ItemActivity; -import com.readrops.app.settings.SettingsActivity; -import com.readrops.app.utils.GlideRequests; -import com.readrops.app.utils.SharedPreferencesManager; -import com.readrops.app.utils.Utils; -import com.readrops.app.utils.customviews.CustomExpandableBadgeDrawerItem; -import com.readrops.app.utils.customviews.ReadropsItemTouchCallback; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.Folder; -import com.readrops.db.entities.Item; -import com.readrops.db.entities.account.Account; -import com.readrops.db.filters.MainFilter; -import com.readrops.db.filters.ListSortType; -import com.readrops.db.pojo.ItemWithFeed; - -import org.jetbrains.annotations.NotNull; -import org.koin.android.compat.ViewModelCompat; -import org.koin.java.KoinJavaComponent; - -import java.lang.ref.WeakReference; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import io.reactivex.CompletableObserver; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.observers.DisposableSingleObserver; -import io.reactivex.schedulers.Schedulers; - -public class MainActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener, - ReadropsItemTouchCallback.SwipeCallback, ActionMode.Callback { - - public static final String TAG = MainActivity.class.getSimpleName(); - - public static final int ADD_FEED_REQUEST = 1; - public static final int MANAGE_ACCOUNT_REQUEST = 2; - public static final int ITEM_REQUEST = 3; - public static final int ADD_ACCOUNT_REQUEST = 4; - public static final int SETTINGS_REQUEST = 5; - - private ActivityMainBinding binding; - private MainItemListAdapter adapter; - - private Drawer drawer; - - private PagedList allItems; - - private MainViewModel viewModel; - private DrawerManager drawerManager; - - private int feedCount; - private int feedNb; - private boolean scrollToTop; - private boolean allItemsSelected; - private boolean updating; - - private ActionMode actionMode; - private Disposable syncDisposable; - - private ItemWithFeed selectedItemWithFeed; - - @Override - protected void onCreate(Bundle savedInstanceState) { - setTheme(R.style.AppTheme_NoActionBar); - super.onCreate(savedInstanceState); - - // surely a better way to do this, but hopefully this code will be replaced with jetpack compose - AccountViewModel accountViewModel = ViewModelCompat.getViewModel(this, AccountViewModel.class); - int accountCount = accountViewModel.getAccountCount() - .subscribeOn(Schedulers.io()) - .blockingGet(); - - if (accountCount == 0) { - Intent intent = new Intent(getApplicationContext(), AccountTypeListActivity.class); - startActivity(intent); - finish(); - } - - binding = ActivityMainBinding.inflate(getLayoutInflater()); - - setContentView(binding.getRoot()); - setSupportActionBar(binding.toolbarMain); - - binding.swipeRefreshLayout.setOnRefreshListener(this); - - feedCount = 0; - initRecyclerView(); - - viewModel = ViewModelCompat.getViewModel(this, MainViewModel.class); - - viewModel.getItemsWithFeed().observe(this, itemWithFeeds -> { - allItems = itemWithFeeds; - - if (!itemWithFeeds.isEmpty()) - binding.emptyListLayout.setVisibility(View.GONE); - else - binding.emptyListLayout.setVisibility(View.VISIBLE); - - if (!binding.swipeRefreshLayout.isRefreshing()) - adapter.submitList(itemWithFeeds); - }); - - drawerManager = new DrawerManager(this, binding.toolbarMain, (view, position, drawerItem) -> { - handleDrawerClick(drawerItem); - - return true; - }); - - drawerManager.setHeaderListener((view, profile, current) -> { - if (!current) { - int id = (int) profile.getIdentifier(); - - switch (id) { - case DrawerManager.ADD_ACCOUNT_ID: - Intent intent = new Intent(this, AccountTypeListActivity.class); - intent.putExtra(FROM_MAIN_ACTIVITY, true); - startActivityForResult(intent, ADD_ACCOUNT_REQUEST); - break; - case DrawerManager.ACCOUNT_SETTINGS_ID: - Intent intent1 = new Intent(this, SettingsActivity.class); - intent1.putExtra(SETTINGS, - SettingsActivity.SettingsKey.ACCOUNT_SETTINGS.ordinal()); - intent1.putExtra(ACCOUNT, viewModel.getCurrentAccount()); - startActivity(intent1); - break; - default: - if (!updating) { - viewModel.setCurrentAccount(id); - updateDrawerFeeds(); - } - break; - } - } else { - Intent intent = new Intent(this, SettingsActivity.class); - intent.putExtra(SETTINGS, - SettingsActivity.SettingsKey.ACCOUNT_SETTINGS.ordinal()); - intent.putExtra(ACCOUNT, viewModel.getCurrentAccount()); - startActivityForResult(intent, MANAGE_ACCOUNT_REQUEST); - } - - return true; - }); - - Account currentAccount = getIntent().getParcelableExtra(ACCOUNT); - WeakReference accountWeakReference = new WeakReference<>(currentAccount); - - viewModel.getAllAccounts().observe(this, accounts -> { - getAccountCredentials(accounts); - viewModel.setAccounts(accounts); - - // the activity was just opened - if (drawer == null) { - int currentAccountId = 0; - if (getIntent().hasExtra(ACCOUNT_ID)) { // coming from a notification - currentAccountId = getIntent().getIntExtra(ACCOUNT_ID, 1); - viewModel.setCurrentAccount(currentAccountId); - } - - drawer = drawerManager.buildDrawer(accounts, currentAccountId); - drawer.setSelection(DrawerManager.ARTICLES_ITEM_ID); - updateDrawerFeeds(); - - openItemActivity(getIntent()); - } else if (accounts.size() < drawerManager.getNumberOfProfiles() && !accounts.isEmpty()) { - drawerManager.updateHeader(accounts); - updateDrawerFeeds(); - } else if (accounts.isEmpty()) { - Intent intent = new Intent(this, AccountTypeListActivity.class); - startActivity(intent); - finish(); - } - - if (accountWeakReference.get() != null && !accountWeakReference.get().isLocal()) { - binding.swipeRefreshLayout.setRefreshing(true); - onRefresh(); - accountWeakReference.clear(); - } else if (currentAccount == null && savedInstanceState != null && savedInstanceState.getBoolean(SYNCING)) { - binding.swipeRefreshLayout.setRefreshing(true); - onRefresh(); - savedInstanceState.clear(); - } - - - }); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - - openItemActivity(intent); - } - - private void openItemActivity(Intent intent) { - if (intent.hasExtra(ITEM_ID) && intent.hasExtra(IMAGE_URL)) { - Intent itemIntent = new Intent(this, ItemActivity.class); - itemIntent.putExtras(intent); - itemIntent.putExtra(ACCOUNT, viewModel.getCurrentAccount()); - - startActivity(itemIntent); - - Item item = new Item(); - item.setId(intent.getIntExtra(ITEM_ID, 0)); - item.setRead(true); - - viewModel.setItemReadState(item) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(throwable -> Utils.showSnackbar(binding.mainRoot, throwable.getMessage())) - .subscribe(); - } - } - - private void handleDrawerClick(IDrawerItem drawerItem) { - if (drawerItem instanceof PrimaryDrawerItem) { - drawer.closeDrawer(); - int id = (int) drawerItem.getIdentifier(); - - switch (id) { - default: - case DrawerManager.ARTICLES_ITEM_ID: - viewModel.setFilterType(MainFilter.ALL); - scrollToTop = true; - viewModel.invalidate(); - setTitle(R.string.articles); - break; - case DrawerManager.READ_LATER_ID: - //viewModel.setFilterType(FilterType.READ_IT_LATER_FILTER); - viewModel.invalidate(); - setTitle(R.string.read_later); - break; - case DrawerManager.STARS_ID: - viewModel.setFilterType(MainFilter.STARS); - viewModel.invalidate(); - setTitle(R.string.favorites); - break; - case DrawerManager.ABOUT_ID: - startAboutActivity(); - break; - case DrawerManager.SETTINGS_ID: - Intent intent = new Intent(getApplication(), SettingsActivity.class); - intent.putExtra(SETTINGS, - SettingsActivity.SettingsKey.SETTINGS.ordinal()); - startActivityForResult(intent, SETTINGS_REQUEST); - break; - } - } else if (drawerItem instanceof SecondaryDrawerItem) { - drawer.closeDrawer(); - - viewModel.setFilterFeedId((int) drawerItem.getIdentifier()); - viewModel.setFilterType(MainFilter.ALL); - viewModel.invalidate(); - setTitle(((SecondaryDrawerItem) drawerItem).getName().getText()); - } else if (drawerItem instanceof CustomExpandableBadgeDrawerItem) { - drawer.closeDrawer(); - - viewModel.setFilerFolderId((int) (drawerItem.getIdentifier() / 1000)); - viewModel.setFilterType(MainFilter.ALL); - viewModel.invalidate(); - setTitle(((CustomExpandableBadgeDrawerItem) drawerItem).getName().getText()); - } - } - - private void updateDrawerFeeds() { - viewModel.getFoldersWithFeeds() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new DisposableSingleObserver>>() { - @Override - public void onSuccess(Map> folderListHashMap) { - drawerManager.updateDrawer(folderListHashMap); - } - - @Override - public void onError(Throwable e) { - Utils.showSnackbar(binding.mainRoot, e.getMessage()); - } - }); - } - - @Override - public void onBackPressed() { - if (drawer.isDrawerOpen()) - drawer.closeDrawer(); - else - super.onBackPressed(); - } - - private void initRecyclerView() { - ViewPreloadSizeProvider preloadSizeProvider = new ViewPreloadSizeProvider(); - adapter = new MainItemListAdapter(KoinJavaComponent.get(GlideRequests.class), preloadSizeProvider); - adapter.setOnItemClickListener(new MainItemListAdapter.OnItemClickListener() { - @Override - public void onItemClick(ItemWithFeed itemWithFeed, int position) { - if (actionMode == null) { - Intent intent = new Intent(getApplicationContext(), ItemActivity.class); - - intent.putExtra(ITEM_ID, itemWithFeed.getItem().getId()); - intent.putExtra(IMAGE_URL, itemWithFeed.getItem().getImageLink()); - intent.putExtra(ACCOUNT, viewModel.getCurrentAccount()); - - startActivityForResult(intent, ITEM_REQUEST); - - itemWithFeed.getItem().setRead(true); - viewModel.setItemReadState(itemWithFeed) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(throwable -> Utils.showSnackbar(binding.mainRoot, throwable.getMessage())) - .subscribe(); - - adapter.notifyItemChanged(position, itemWithFeed); - updateDrawerFeeds(); - } else { - adapter.toggleSelection(position); - int selectionSize = adapter.getSelection().size(); - - if (selectionSize > 0) - actionMode.setTitle(String.valueOf(selectionSize)); - else - actionMode.finish(); - } - } - - @Override - public void onItemLongClick(ItemWithFeed itemWithFeed, int position) { - if (actionMode != null || binding.swipeRefreshLayout.isRefreshing()) - return; - - selectedItemWithFeed = itemWithFeed; - adapter.toggleSelection(position); - - actionMode = startActionMode(MainActivity.this); - actionMode.setTitle(String.valueOf(adapter.getSelection().size())); - } - }); - - RecyclerViewPreloader preloader = new RecyclerViewPreloader(Glide.with(this), adapter, preloadSizeProvider, 10); - binding.itemsRecyclerView.addOnScrollListener(preloader); - - binding.itemsRecyclerView.addRecyclerListener(viewHolder -> { - MainItemListAdapter.ItemViewHolder vh = (MainItemListAdapter.ItemViewHolder) viewHolder; - KoinJavaComponent.get(GlideRequests.class).clear(vh.getItemImage()); - }); - - LinearLayoutManager layoutManager = new LinearLayoutManager(this); - binding.itemsRecyclerView.setLayoutManager(layoutManager); - - DividerItemDecoration decoration = new DividerItemDecoration(this, layoutManager.getOrientation()); - binding.itemsRecyclerView.addItemDecoration(decoration); - - binding.itemsRecyclerView.setAdapter(adapter); - - - Drawable readLater = ContextCompat.getDrawable(this, R.drawable.ic_read_later).mutate(); - DrawableCompat.setTint(readLater, ContextCompat.getColor(this, android.R.color.white)); - - new ItemTouchHelper(new ReadropsItemTouchCallback(this, - new ReadropsItemTouchCallback.Config.Builder() - .swipeDirs(ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) - .swipeCallback(this) - .leftDraw(ContextCompat.getColor(this, R.color.colorAccent), R.drawable.ic_read_later, readLater) - .rightDraw(ContextCompat.getColor(this, R.color.colorAccent), R.drawable.ic_read, null) - .build())) - .attachToRecyclerView(binding.itemsRecyclerView); - - adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { - @Override - public void onItemRangeInserted(int positionStart, int itemCount) { - if (scrollToTop) { - binding.itemsRecyclerView.scrollToPosition(0); - scrollToTop = false; - } - } - - @Override - public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { - if (scrollToTop) { - binding.itemsRecyclerView.scrollToPosition(0); - scrollToTop = false; - } else - super.onItemRangeMoved(fromPosition, toPosition, itemCount); - } - }); - - binding.itemsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - if (dy > 0) { - binding.addFeedFab.hide(); - } else { - binding.addFeedFab.show(); - } - - int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition(); - if (firstVisibleItemPosition - 2 >= 0) { - Item item = adapter.getItemWithFeed(firstVisibleItemPosition - 2).getItem(); - - // Might be better to have a global variable updated when going back from settings - if (!item.isRead() && SharedPreferencesManager.readBoolean(SharedPreferencesManager - .SharedPrefKey.MARK_ITEMS_READ_ON_SCROLL)) { - item.setRead(!item.isRead()); - - viewModel.setItemReadState(item) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(throwable -> Utils.showSnackbar(binding.mainRoot, throwable.getMessage())) - .subscribe(); - } - } - } - }); - } - - @Override - public void onSwipe(@NotNull RecyclerView.ViewHolder viewHolder, int direction) { - Item item = adapter.getItemWithFeed(viewHolder.getBindingAdapterPosition()).getItem(); - - if (direction == ItemTouchHelper.LEFT) { // set item read state - item.setRead(!item.isRead()); - - viewModel.setItemReadState(item) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(throwable -> Utils.showSnackbar(binding.mainRoot, throwable.getMessage())) - .subscribe(); - - } else { // set item read it later state - item.setReadItLater(!item.isReadItLater()); - - viewModel.setItemReadItLater(item.isReadItLater(), item.getId()) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(throwable -> Utils.showSnackbar(binding.mainRoot, throwable.getMessage())) - .subscribe(); - } - - adapter.notifyItemChanged(viewHolder.getBindingAdapterPosition()); - } - - @Override - public boolean onCreateActionMode(ActionMode actionMode, Menu menu) { - drawer.getDrawerLayout().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); - binding.swipeRefreshLayout.setEnabled(false); - - actionMode.getMenuInflater().inflate(R.menu.item_list_contextual_menu, menu); - getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.primary_dark)); - - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) { - menu.findItem(R.id.item_mark_read).setVisible(!selectedItemWithFeed.getItem().isRead()); - menu.findItem(R.id.item_mark_unread).setVisible(selectedItemWithFeed.getItem().isRead()); - - return true; - } - - @Override - public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) { - int itemId = menuItem.getItemId(); - - if (itemId == R.id.item_mark_read) { - setReadState(true); - } else if (itemId == R.id.item_mark_unread) { - setReadState(false); - } else if (itemId == R.id.item_select_all) { - if (allItemsSelected) { - adapter.unselectAll(); - allItemsSelected = false; - actionMode.finish(); - } else { - adapter.selectAll(); - allItemsSelected = true; - } - } - - return true; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - mode.finish(); - actionMode = null; - - drawer.getDrawerLayout().setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); - binding.swipeRefreshLayout.setEnabled(true); - - adapter.clearSelection(); - } - - private void setReadState(boolean read) { - if (allItemsSelected) { - viewModel.setAllItemsReadState(read) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(throwable -> Utils.showSnackbar(binding.mainRoot, throwable.getMessage())) - .subscribe(); - - allItemsSelected = false; - } else { - viewModel.setItemsReadState(adapter.getSelectedItems(), read) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError(throwable -> Utils.showSnackbar(binding.mainRoot, throwable.getMessage())) - .subscribe(); - } - - adapter.updateSelection(read); - updateDrawerFeeds(); - actionMode.finish(); - } - - @Override - public void onRefresh() { - Log.d(TAG, "syncing started"); - drawerManager.disableAccountSelection(); - updating = true; - - if (viewModel.isAccountLocal()) { - viewModel.getFeedCount() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new DisposableSingleObserver() { - @Override - public void onSuccess(@NonNull Integer integer) { - feedNb = integer; - sync(null); - } - - @Override - public void onError(@NonNull Throwable e) { - Utils.showSnackbar(binding.mainRoot, e.getMessage()); - } - }); - } else { - sync(null); - } - } - - public void openAddFeedActivity(View view) { - Intent intent = new Intent(this, AddFeedActivity.class); - intent.putExtra(ACCOUNT_ID, viewModel.getCurrentAccount().getId()); - startActivityForResult(intent, ADD_FEED_REQUEST); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - if (requestCode == ADD_FEED_REQUEST && resultCode == RESULT_OK && data != null) { - List feeds = data.getParcelableArrayListExtra(FEEDS); - - if (feeds != null && !feeds.isEmpty() && viewModel.isAccountLocal()) { - binding.swipeRefreshLayout.setRefreshing(true); - feedNb = feeds.size(); - sync(feeds); - } - - } else if (requestCode == MANAGE_ACCOUNT_REQUEST || requestCode == SETTINGS_REQUEST) { - updateDrawerFeeds(); - - } else if (requestCode == ADD_ACCOUNT_REQUEST && resultCode == RESULT_OK && data != null) { - Account newAccount = data.getParcelableExtra(ACCOUNT); - - if (newAccount != null) { - // get credentials before creating the repository - if (!newAccount.isLocal()) { - getAccountCredentials(Collections.singletonList(newAccount)); - } - - viewModel.addAccount(newAccount); - adapter.clearData(); - - // start syncing only if the account is not local - if (!viewModel.isAccountLocal()) { - binding.swipeRefreshLayout.setRefreshing(true); - onRefresh(); - } - - drawerManager.resetItems(); - drawerManager.addAccount(newAccount, true); - } - - } - - super.onActivityResult(requestCode, resultCode, data); - } - - private void sync(@Nullable List feeds) { - viewModel.sync(feeds, feed -> { - if (viewModel.isAccountLocal() && feedNb > 0) { - binding.syncProgressTextView.setText(getString(R.string.updating_feed, feed.getName())); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - binding.syncProgressBar.setProgress((feedCount * 100) / feedNb, true); - } else - binding.syncProgressBar.setProgress((feedCount * 100) / feedNb); - } - - feedCount++; - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new CompletableObserver() { - @Override - public void onSubscribe(@NonNull Disposable d) { - syncDisposable = d; - - if (viewModel.isAccountLocal() && feedNb > 0) { - binding.syncProgressLayout.setVisibility(View.VISIBLE); - binding.syncProgressBar.setProgress(0); - } - } - - @Override - public void onComplete() { - viewModel.invalidate(); - - if (viewModel.isAccountLocal() && feedNb > 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - binding.syncProgressBar.setProgress(100, true); - else - binding.syncProgressBar.setProgress(100); - - binding.syncProgressLayout.setVisibility(View.GONE); - } - - binding.swipeRefreshLayout.setRefreshing(false); - - scrollToTop = true; - adapter.submitList(allItems); - - drawerManager.enableAccountSelection(); - updateDrawerFeeds(); // update drawer after syncing feeds - updating = false; - } - - @Override - public void onError(@NonNull Throwable e) { - binding.swipeRefreshLayout.setRefreshing(false); - binding.syncProgressLayout.setVisibility(View.GONE); - - Utils.showSnackbar(binding.mainRoot, e.getMessage()); - drawerManager.enableAccountSelection(); - updating = false; - } - }); - } - - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.item_list_menu, menu); - - MenuItem articlesItem = menu.findItem(R.id.item_filter_read_items); - articlesItem.setChecked(viewModel.showReadItems()); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int itemId = item.getItemId(); - - if (itemId == R.id.item_filter_read_items) { - if (item.isChecked()) { - item.setChecked(false); - viewModel.setShowReadItems(false); - SharedPreferencesManager.writeValue( - SharedPreferencesManager.SharedPrefKey.SHOW_READ_ARTICLES, false); - } else { - item.setChecked(true); - viewModel.setShowReadItems(true); - SharedPreferencesManager.writeValue( - SharedPreferencesManager.SharedPrefKey.SHOW_READ_ARTICLES, true); - } - - viewModel.invalidate(); - return true; - } else if (itemId == R.id.item_sort) { - displayFilterDialog(); - return true; - } else if (itemId == R.id.start_sync) { - if (!viewModel.isAccountLocal()) { - binding.swipeRefreshLayout.setRefreshing(true); - } - onRefresh(); - } - - return super.onOptionsItemSelected(item); - } - - private void displayFilterDialog() { - int index = viewModel.getSortType() == ListSortType.OLDEST_TO_NEWEST ? 1 : 0; - - new MaterialDialog.Builder(this) - .title(R.string.filter) - .items(R.array.filter_items) - .itemsCallbackSingleChoice(index, (dialog, itemView, which, text) -> { - String[] items = getResources().getStringArray(R.array.filter_items); - - if (text.toString().equals(items[0])) - viewModel.setSortType(ListSortType.NEWEST_TO_OLDEST); - else - viewModel.setSortType(ListSortType.OLDEST_TO_NEWEST); - - scrollToTop = true; - viewModel.invalidate(); - return true; - }) - .show(); - } - - private void getAccountCredentials(List accounts) { - for (Account account : accounts) { - if (account.getLogin() == null) - account.setLogin(SharedPreferencesManager.readString(account.getLoginKey())); - - if (account.getPassword() == null) - account.setPassword(SharedPreferencesManager.readString(account.getPasswordKey())); - } - } - - private void startAboutActivity() { - Libs.ActivityStyle activityStyle; - int uiMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - - if (uiMode == Configuration.UI_MODE_NIGHT_YES) { - activityStyle = Libs.ActivityStyle.DARK; - } else { - activityStyle = Libs.ActivityStyle.LIGHT_DARK_TOOLBAR; - } - - new LibsBuilder() - .withAboutIconShown(true) - .withAboutVersionShown(true) - .withAboutAppName(getString(R.string.app_name)) - .withAboutDescription(getString(R.string.app_description)) - .withLicenseShown(true) - .withLicenseDialog(false) - .withActivityTitle(getString(R.string.about)) - .withActivityStyle(activityStyle) - .withFields(R.string.class.getFields()) - .withAboutSpecial1(getString(R.string.source_code)) - .withAboutSpecial2(getString(R.string.changelog)) - .withListener(new LibsConfiguration.LibsListener() { - @Override - public void onIconClicked(View v) { - - } - - @Override - public boolean onLibraryAuthorClicked(View v, Library library) { - return false; - } - - @Override - public boolean onLibraryContentClicked(View v, Library library) { - return false; - } - - @Override - public boolean onLibraryBottomClicked(View v, Library library) { - return false; - } - - @Override - public boolean onExtraClicked(View v, Libs.SpecialButton specialButton) { - if (v.getId() == R.id.aboutSpecial1) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.app_url)))); - } else if (v.getId() == R.id.aboutSpecial2) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.changelog_url)))); - } - - return false; - } - - @Override - public boolean onIconLongClicked(View v) { - return false; - } - - @Override - public boolean onLibraryAuthorLongClicked(View v, Library library) { - return false; - } - - @Override - public boolean onLibraryContentLongClicked(View v, Library library) { - return false; - } - - @Override - public boolean onLibraryBottomLongClicked(View v, Library library) { - return false; - } - }) - .start(this); - } - - @Override - protected void onDestroy() { - if (syncDisposable != null && !syncDisposable.isDisposed()) - syncDisposable.dispose(); - - super.onDestroy(); - } - - @Override - protected void onSaveInstanceState(@NonNull Bundle outState) { - if (binding.swipeRefreshLayout.isRefreshing()) - outState.putBoolean(SYNCING, true); - - super.onSaveInstanceState(outState); - } -} diff --git a/app/src/main/java/com/readrops/app/itemslist/MainItemListAdapter.java b/app/src/main/java/com/readrops/app/itemslist/MainItemListAdapter.java deleted file mode 100644 index 1476bf21..00000000 --- a/app/src/main/java/com/readrops/app/itemslist/MainItemListAdapter.java +++ /dev/null @@ -1,372 +0,0 @@ -package com.readrops.app.itemslist; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import androidx.paging.PagedListAdapter; -import androidx.recyclerview.widget.DiffUtil; -import androidx.recyclerview.widget.RecyclerView; - -import com.bumptech.glide.ListPreloader; -import com.bumptech.glide.RequestBuilder; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.load.resource.bitmap.CenterCrop; -import com.bumptech.glide.load.resource.bitmap.RoundedCorners; -import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; -import com.bumptech.glide.request.RequestOptions; -import com.bumptech.glide.request.transition.DrawableCrossFadeFactory; -import com.bumptech.glide.util.ViewPreloadSizeProvider; -import com.readrops.api.utils.DateUtils; -import com.readrops.app.R; -import com.readrops.app.databinding.ListItemBinding; -import com.readrops.app.utils.GlideRequests; -import com.readrops.app.utils.Utils; -import com.readrops.db.entities.Item; -import com.readrops.db.pojo.ItemWithFeed; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -public class MainItemListAdapter extends PagedListAdapter implements ListPreloader.PreloadModelProvider { - - private GlideRequests glideRequests; - private OnItemClickListener listener; - private ViewPreloadSizeProvider preloadSizeProvider; - - private LinkedHashSet selection; - - public MainItemListAdapter(GlideRequests glideRequests, ViewPreloadSizeProvider preloadSizeProvider) { - super(DIFF_CALLBACK); - - this.glideRequests = glideRequests; - this.preloadSizeProvider = preloadSizeProvider; - selection = new LinkedHashSet<>(); - } - - private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { - @Override - public boolean areItemsTheSame(@NonNull ItemWithFeed item, @NonNull ItemWithFeed t1) { - return item.getItem().getId() == t1.getItem().getId(); - } - - @Override - public boolean areContentsTheSame(@NonNull ItemWithFeed itemWithFeed, @NonNull ItemWithFeed t1) { - Item oldItem = itemWithFeed.getItem(); - Item newItem = t1.getItem(); - - boolean folder = false; - if (itemWithFeed.getFolder() != null && t1.getFolder() != null) - folder = itemWithFeed.getFolder().getName().equals(t1.getFolder().getName()); - - return oldItem.getTitle().equals(newItem.getTitle()) && - itemWithFeed.getFeedName().equals(t1.getFeedName()) && - folder && - oldItem.isRead() == newItem.isRead() && - oldItem.isReadItLater() == newItem.isReadItLater() && - itemWithFeed.getColor() == t1.getColor() && - itemWithFeed.getBgColor() == t1.getBgColor(); - } - - @Override - public Object getChangePayload(@NonNull ItemWithFeed oldItem, @NonNull ItemWithFeed newItem) { - return newItem; - } - }; - - private static final DrawableCrossFadeFactory FADE_FACTORY = new DrawableCrossFadeFactory.Builder().setCrossFadeEnabled(true).build(); - - private static final RequestOptions REQUEST_OPTIONS = new RequestOptions().transform(new CenterCrop(), new RoundedCorners(16)); - - @NonNull - @Override - public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { - ListItemBinding binding = ListItemBinding.inflate(LayoutInflater.from(viewGroup.getContext())); - - ItemViewHolder viewHolder = new ItemViewHolder(binding); - preloadSizeProvider.setView(binding.itemImage); - - return viewHolder; - } - - @Override - public void onBindViewHolder(@NonNull ItemViewHolder holder, int position, @NonNull List payloads) { - if (!payloads.isEmpty()) { - ItemWithFeed itemWithFeed = (ItemWithFeed) payloads.get(0); - - holder.bind(itemWithFeed); - holder.applyColors(itemWithFeed); - - if (itemWithFeed.getFolder() != null) - holder.binding.itemFolderName.setText(itemWithFeed.getFolder().getName()); - else - holder.binding.itemFolderName.setText(R.string.no_folder); - - holder.setReadState(itemWithFeed.getItem().isRead()); - holder.setSelected(selection.contains(position)); - } else - onBindViewHolder(holder, position); - } - - @Override - public void onBindViewHolder(@NonNull ItemViewHolder viewHolder, int i) { - ItemWithFeed itemWithFeed = getItem(i); - if (itemWithFeed == null) - return; - - viewHolder.bind(itemWithFeed); - viewHolder.setImages(itemWithFeed); - viewHolder.applyColors(itemWithFeed); - - int minutes = (int) Math.round(itemWithFeed.getItem().getReadTime()); - if (minutes < 1) - viewHolder.binding.itemReadtime.setText(R.string.read_time_lower_than_1); - else if (minutes > 1) - viewHolder.binding.itemReadtime.setText(viewHolder.itemView.getContext(). - getString(R.string.read_time, String.valueOf(minutes))); - else - viewHolder.binding.itemReadtime.setText(R.string.read_time_one_minute); - - if (itemWithFeed.getFolder() != null) - viewHolder.binding.itemFolderName.setText(itemWithFeed.getFolder().getName()); - else - viewHolder.binding.itemFolderName.setText(R.string.no_folder); - - viewHolder.setReadState(itemWithFeed.getItem().isRead()); - viewHolder.setSelected(selection.contains(viewHolder.getAdapterPosition())); - } - - - @Override - public long getItemId(int position) { - return getItem(position).getItem().getId(); - } - - public void toggleSelection(int position) { - if (selection.contains(position)) - selection.remove(position); - else - selection.add(position); - - notifyItemChanged(position, getItem(position)); - } - - public void clearSelection() { - LinkedHashSet localSelection = new LinkedHashSet<>(selection); - selection.clear(); - - for (int position : localSelection) { - notifyItemChanged(position, getItem(position)); - } - } - - public Set getSelection() { - return selection; - } - - public void updateSelection(boolean read) { - for (int position : selection) { - ItemWithFeed itemWithFeed = getItem(position); - itemWithFeed.getItem().setRead(read); - notifyItemChanged(position, itemWithFeed); - } - } - - public void selectAll() { - selection.clear(); - for (int i = 0; i < getItemCount(); i++) { - selection.add(i); - } - - notifyDataSetChanged(); - } - - public void unselectAll() { - selection.clear(); - notifyDataSetChanged(); - } - - public List getSelectedItems() { - List items = new ArrayList<>(); - - for (int i : selection) { - items.add(getItem(i)); - } - - return items; - } - - public void clearData() { - submitList(null); - } - - public ItemWithFeed getItemWithFeed(int i) { - return getItem(i); - } - - @NonNull - @Override - public List getPreloadItems(int position) { - if (getItem(position).getItem().getHasImage()) { - String url = getItem(position).getItem().getImageLink(); - return Collections.singletonList(url); - } else { - return Collections.emptyList(); - } - } - - @Nullable - @Override - public RequestBuilder getPreloadRequestBuilder(@NonNull String url) { - return glideRequests - .load(url) - .centerCrop() - .apply(REQUEST_OPTIONS) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .transition(DrawableTransitionOptions.withCrossFade(FADE_FACTORY)); - } - - public interface OnItemClickListener { - void onItemClick(ItemWithFeed itemWithFeed, int position); - - void onItemLongClick(ItemWithFeed itemWithFeed, int position); - } - - public void setOnItemClickListener(OnItemClickListener listener) { - this.listener = listener; - } - - public class ItemViewHolder extends RecyclerView.ViewHolder { - - private ListItemBinding binding; - private View[] alphaViews; - - ItemViewHolder(ListItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; - - itemView.setOnClickListener((view -> { - int position = getAdapterPosition(); - - if (listener != null && position != RecyclerView.NO_POSITION) - listener.onItemClick(getItem(position), position); - })); - - itemView.setOnLongClickListener(v -> { - int position = getAdapterPosition(); - - if (listener != null && position != RecyclerView.NO_POSITION) - listener.onItemLongClick(getItem(position), position); - - return true; - }); - - alphaViews = new View[]{ - binding.itemDate, - binding.itemFolderName, - binding.itemFeedIcon, - binding.itemFeedName, - binding.itemDescription, - binding.itemTitle, - binding.itemImage, - binding.itemReadtimeLayout - }; - } - - private void bind(ItemWithFeed itemWithFeed) { - Item item = itemWithFeed.getItem(); - - binding.itemTitle.setText(item.getTitle()); - binding.itemDate.setText(DateUtils.formattedDateByLocal(item.getPubDate())); - binding.itemFeedName.setText(itemWithFeed.getFeedName()); - - if (item.getCleanDescription() != null) { - binding.itemDescription.setVisibility(View.VISIBLE); - binding.itemDescription.setText(item.getCleanDescription()); - } else { - binding.itemDescription.setVisibility(View.GONE); - if (itemWithFeed.getItem().getHasImage()) - binding.itemTitle.setMaxLines(4); - } - } - - private void setImages(ItemWithFeed itemWithFeed) { - if (itemWithFeed.getItem().getHasImage()) { - binding.itemImage.setVisibility(View.VISIBLE); - - glideRequests - .load(itemWithFeed.getItem().getImageLink()) - .centerCrop() - .apply(REQUEST_OPTIONS) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .transition(DrawableTransitionOptions.withCrossFade(FADE_FACTORY)) - .into(binding.itemImage); - } else - binding.itemImage.setVisibility(View.GONE); - - if (itemWithFeed.getFeedIconUrl() != null) { - glideRequests. - load(itemWithFeed.getFeedIconUrl()) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .placeholder(R.drawable.ic_rss_feed_grey) - .into(binding.itemFeedIcon); - } else - binding.itemFeedIcon.setImageResource(R.drawable.ic_rss_feed_grey); - } - - private void applyColors(ItemWithFeed itemWithFeed) { - Resources resources = itemView.getResources(); - - if (itemWithFeed.getBgColor() != 0) { - binding.itemFeedName.setTextColor(itemWithFeed.getBgColor()); - Utils.setDrawableColor(binding.itemDate.getBackground(), itemWithFeed.getBgColor()); - - } else if (itemWithFeed.getColor() != 0) { - binding.itemFeedName.setTextColor(itemWithFeed.getColor()); - Utils.setDrawableColor(binding.itemDate.getBackground(), itemWithFeed.getColor()); - - } else if (itemWithFeed.getBgColor() == 0 && itemWithFeed.getColor() == 0) { - binding.itemFeedName.setTextColor(resources.getColor(android.R.color.tab_indicator_text)); - Utils.setDrawableColor(binding.itemDate.getBackground(), - ContextCompat.getColor(itemView.getContext(), R.color.colorPrimary)); - } - } - - private void setReadState(boolean isRead) { - float alpha = isRead ? 0.5f : 1.0f; - for (View view : alphaViews) { - view.setAlpha(alpha); - } - } - - private void setSelected(boolean selected) { - Context context = itemView.getContext(); - TypedValue outValue = new TypedValue(); - - if (selected) { - context.getTheme().resolveAttribute( - android.R.attr.colorControlHighlight, outValue, true); - } else { - context.getTheme().resolveAttribute( - android.R.attr.selectableItemBackground, outValue, true); - } - - itemView.setBackgroundResource(outValue.resourceId); - } - - public ImageView getItemImage() { - return binding.itemImage; - } - } -} diff --git a/app/src/main/java/com/readrops/app/itemslist/MainViewModel.java b/app/src/main/java/com/readrops/app/itemslist/MainViewModel.java deleted file mode 100644 index cdb201b7..00000000 --- a/app/src/main/java/com/readrops/app/itemslist/MainViewModel.java +++ /dev/null @@ -1,266 +0,0 @@ -package com.readrops.app.itemslist; - -import androidx.annotation.NonNull; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MediatorLiveData; -import androidx.lifecycle.ViewModel; -import androidx.paging.DataSource; -import androidx.paging.LivePagedListBuilder; -import androidx.paging.PagedList; - -import com.readrops.app.repositories.ARepository; -import com.readrops.app.repositories.FeedUpdate; -import com.readrops.app.utils.SharedPreferencesManager; -import com.readrops.db.Database; -import com.readrops.db.RoomFactoryWrapper; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.Folder; -import com.readrops.db.entities.Item; -import com.readrops.db.entities.account.Account; -import com.readrops.db.filters.MainFilter; -import com.readrops.db.filters.ListSortType; -import com.readrops.db.pojo.ItemWithFeed; -import com.readrops.db.queries.ItemsQueryBuilder; -import com.readrops.db.queries.QueryFilters; - -import org.koin.core.parameter.ParametersHolderKt; -import org.koin.java.KoinJavaComponent; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import io.reactivex.Completable; -import io.reactivex.Single; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; - -public class MainViewModel extends ViewModel { - - private final MediatorLiveData> itemsWithFeed; - private LiveData> lastFetch; - private ARepository repository; - private final Database database; - - private final QueryFilters queryFilters; - - private Account currentAccount; - private List accounts; - - public MainViewModel(@NonNull Database database) { - this.database = database; - itemsWithFeed = new MediatorLiveData<>(); - - queryFilters = new QueryFilters(); - /* queryFilters.setShowReadItems(SharedPreferencesManager.readBoolean( - SharedPreferencesManager.SharedPrefKey.SHOW_READ_ARTICLES));*/ - } - - //region main query - - private void setRepository() { - repository = KoinJavaComponent.get(ARepository.class, null, - () -> ParametersHolderKt.parametersOf(currentAccount)); - } - - private void buildPagedList() { - if (lastFetch != null) { - itemsWithFeed.removeSource(lastFetch); - } - - DataSource.Factory items; - items = database.itemDao().selectAll(ItemsQueryBuilder.INSTANCE.buildItemsQuery(queryFilters, currentAccount.getConfig().getUseSeparateState())); - - lastFetch = new LivePagedListBuilder<>(new RoomFactoryWrapper<>(items), - new PagedList.Config.Builder() - .setPageSize(100) - .setPrefetchDistance(150) - .setEnablePlaceholders(false) - .build()) - .build(); - - itemsWithFeed.addSource(lastFetch, itemsWithFeed::setValue); - } - - public void invalidate() { - buildPagedList(); - } - - public void setShowReadItems(boolean showReadItems) { - //queryFilters.setShowReadItems(showReadItems); - } - - public boolean showReadItems() { - return queryFilters.getShowReadItems(); - } - - public void setFilterType(MainFilter filterType) { - //queryFilters.setMainFilter(filterType); - } - - public MainFilter getFilterType() { - return queryFilters.getMainFilter(); - } - - public void setSortType(ListSortType sortType) { - //queryFilters.setSortType(sortType); - } - - public ListSortType getSortType() { - return queryFilters.getSortType(); - } - - public void setFilterFeedId(int filterFeedId) { - //queryFilters.setFilterFeedId(filterFeedId); - } - - public void setFilerFolderId(int folderId) { - //queryFilters.setFilterFolderId(folderId); - } - - public MediatorLiveData> getItemsWithFeed() { - return itemsWithFeed; - } - - public Completable sync(List feeds, FeedUpdate update) { - itemsWithFeed.removeSource(lastFetch); - - // get current viewed feed - if (feeds == null && queryFilters.getMainFilter() == MainFilter.ALL) { - return Single.create(emitter -> emitter.onSuccess(database.feedDao() - .getFeedById(queryFilters.getFilterFeedId()))) - .flatMapCompletable(feed -> repository.sync(Collections.singletonList(feed), update)); - } - - return repository.sync(feeds, update); - } - - public Single getFeedCount() { - return repository.getFeedCount(currentAccount.getId()); - } - - public Single>> getFoldersWithFeeds() { - return repository.getFoldersWithFeeds(); - } - - //endregion - - //region Account - - public LiveData> getAllAccounts() { - return database.accountDao().selectAllAsync(); - } - - private Completable deselectOldCurrentAccount(int accountId) { - return Completable.create(emitter -> { - database.accountDao().deselectOldCurrentAccount(accountId); - emitter.onComplete(); - }); - } - - private Account getAccount(int id) { - for (Account account : accounts) { - if (account.getId() == id) - return account; - } - - return null; - } - - public void addAccount(Account account) { - accounts.add(account); - setCurrentAccount(account); - } - - public Account getCurrentAccount() { - return currentAccount; - } - - public void setCurrentAccount(Account currentAccount) { - this.currentAccount = currentAccount; - setRepository(); - //queryFilters.setAccountId(currentAccount.getId()); - buildPagedList(); - - // set the new account as the current one - Completable setCurrentAccount = Completable.create(emitter -> { - database.accountDao().setCurrentAccount(currentAccount.getId()); - emitter.onComplete(); - }); - - Completable.concat(Arrays.asList(setCurrentAccount, deselectOldCurrentAccount(currentAccount.getId()))) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(); - } - - public void setCurrentAccount(int id) { - setCurrentAccount(getAccount(id)); - } - - - public void setAccounts(List accounts) { - this.accounts = accounts; - - boolean currentAccountExists = false; - - for (Account account1 : accounts) { - if (account1.isCurrentAccount()) { - currentAccount = account1; - currentAccountExists = true; - - setRepository(); - //queryFilters.setAccountId(currentAccount.getId()); - buildPagedList(); - break; - } - } - - if (!currentAccountExists && !accounts.isEmpty()) { - setCurrentAccount(accounts.get(0)); - accounts.get(0).setCurrentAccount(true); - } - } - - public boolean isAccountLocal() { - return currentAccount.isLocal(); - } - - //endregion - - //region Item read state - - public Completable setItemReadState(ItemWithFeed itemWithFeed) { - return repository.setItemReadState(itemWithFeed.getItem()); - } - - public Completable setItemReadState(Item item) { - return repository.setItemReadState(item); - } - - public Completable setItemsReadState(List items, boolean read) { - List completableList = new ArrayList<>(); - - for (ItemWithFeed itemWithFeed : items) { - itemWithFeed.getItem().setRead(read); - completableList.add(setItemReadState(itemWithFeed)); - } - - return Completable.concat(completableList); - } - - public Completable setAllItemsReadState(boolean read) { - if (queryFilters.getMainFilter() == MainFilter.ALL) - return repository.setAllFeedItemsReadState(queryFilters.getFilterFeedId(), read); - else - return repository.setAllItemsReadState(read); - } - - public Completable setItemReadItLater(boolean readLater, int itemId) { - return database.itemDao().setReadItLater(readLater, itemId); - } - - //endregion -} diff --git a/app/src/main/java/com/readrops/app/notifications/NotificationPermissionActivity.kt b/app/src/main/java/com/readrops/app/notifications/NotificationPermissionActivity.kt deleted file mode 100644 index 46230e4e..00000000 --- a/app/src/main/java/com/readrops/app/notifications/NotificationPermissionActivity.kt +++ /dev/null @@ -1,150 +0,0 @@ -package com.readrops.app.notifications - -import android.content.Intent -import android.os.Bundle -import android.view.MenuItem -import androidx.activity.viewModels -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.Observer -import androidx.recyclerview.widget.LinearLayoutManager -import com.afollestad.materialdialogs.MaterialDialog -import com.readrops.app.R -import com.readrops.app.settings.SettingsActivity -import com.readrops.app.databinding.ActivityNotificationPermissionBinding -import com.readrops.app.utils.ReadropsKeys -import com.readrops.app.utils.ReadropsKeys.ACCOUNT_ID -import com.readrops.app.utils.SharedPreferencesManager -import com.readrops.app.utils.Utils -import com.readrops.db.entities.Feed -import com.readrops.db.entities.account.Account -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers -import org.koin.androidx.viewmodel.ext.android.getViewModel - -class NotificationPermissionActivity : AppCompatActivity() { - - private lateinit var binding: ActivityNotificationPermissionBinding - private lateinit var viewModel: NotificationPermissionViewModel - private var adapter: NotificationPermissionListAdapter? = null - - private var isFirstCheck = true - private var feedStateChanged = false - private var feeds = listOf() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityNotificationPermissionBinding.inflate(layoutInflater) - setContentView(binding.root) - - setTitle(R.string.notifications) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - - val accountId = intent.getIntExtra(ACCOUNT_ID, 0) - - viewModel = getViewModel() - viewModel.getAccount(accountId).observe(this, Observer { account -> - viewModel.account = account - - if (adapter == null) { - // execute the method only once - setupUI(account) - } - }) - } - - private fun setupUI(account: Account) { - binding.notifPermissionAccountSwitch.isChecked = account.isNotificationsEnabled - binding.notifPermissionAccountSwitch.setOnCheckedChangeListener { _, isChecked -> - account.isNotificationsEnabled = isChecked - binding.notifPermissionFeedsSwitch.isEnabled = isChecked - - adapter?.enableAll = isChecked - adapter?.notifyDataSetChanged() - - viewModel.setAccountNotificationsState(isChecked) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError { Utils.showSnackbar(binding.root, it.message) } - .subscribe() - - if (isChecked) displayAutoSynchroPopup() - } - - binding.notifPermissionFeedsSwitch.isEnabled = account.isNotificationsEnabled - binding.notifPermissionFeedsSwitch.setOnCheckedChangeListener { _, isChecked -> - if (canUpdateAllFeedsPermissions(isChecked)) { - viewModel.setAllFeedsNotificationState(isChecked) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError { Utils.showSnackbar(binding.root, it.message) } - .subscribe() - } - - if (isFirstCheck) isFirstCheck = false - if (feedStateChanged) feedStateChanged = false - } - - adapter = NotificationPermissionListAdapter(account.isNotificationsEnabled) { feed -> - feedStateChanged = true - - viewModel.setFeedNotificationState(feed) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnError { Utils.showSnackbar(binding.root, it.message) } - .subscribe() - } - - binding.notifPermissionAccountList.layoutManager = LinearLayoutManager(this) - binding.notifPermissionAccountList.adapter = adapter - - viewModel.getFeedsWithNotifPermission().observe(this, Observer { newFeeds -> - feeds = newFeeds - - binding.notifPermissionFeedsSwitch.isChecked = newFeeds.all { it.isNotificationEnabled } - adapter?.submitList(newFeeds) - }) - } - - /** - * Inform if is possible to update all feeds notifications permissions in the same time. - * The method takes into account the following states : - * - first check : when opening the activity with all feeds permissions enabled, - * the enable all feeds permissions switch will be checked but the request mustn't be executed - * - feed state : if all feeds permissions are enabled and a feed permission is disabled, - * the enable all feeds permissions switch will be unchecked but the request mustn't be executed as only one feed permission is disabled - * - all feeds permissions switch checked : if the setOnCheckedChangeListener method is triggered because all feeds permissions were enabled, - * do not execute the request as it would be pointless - */ - private fun canUpdateAllFeedsPermissions(isChecked: Boolean): Boolean { - return (!isFirstCheck || !feeds.all { it.isNotificationEnabled }) && - (!feedStateChanged || (isChecked && !feeds.all { it.isNotificationEnabled })) - } - - private fun displayAutoSynchroPopup() { - val autoSynchroValue = SharedPreferencesManager.readString(SharedPreferencesManager.SharedPrefKey.AUTO_SYNCHRO) - - if (autoSynchroValue.toFloat() <= 0) { - MaterialDialog.Builder(this) - .title(R.string.auto_synchro_disabled) - .content(R.string.enable_auto_synchro_text) - .positiveText(R.string.open) - .neutralText(R.string.cancel) - .onPositive { _, _ -> - val intent = Intent(this, SettingsActivity::class.java).apply { - putExtra(ReadropsKeys.SETTINGS, SettingsActivity.SettingsKey.SETTINGS.ordinal) - } - - startActivity(intent) - } - .show() - } - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> finish() - } - - return super.onOptionsItemSelected(item) - } -} diff --git a/app/src/main/java/com/readrops/app/notifications/NotificationPermissionListAdapter.kt b/app/src/main/java/com/readrops/app/notifications/NotificationPermissionListAdapter.kt deleted file mode 100644 index 01cd67f0..00000000 --- a/app/src/main/java/com/readrops/app/notifications/NotificationPermissionListAdapter.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.readrops.app.notifications - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.readrops.app.R -import com.readrops.app.databinding.NotificationPermissionLayoutBinding -import com.readrops.app.utils.GlideRequests -import com.readrops.db.entities.Feed -import org.koin.core.component.KoinComponent -import org.koin.core.component.get - -class NotificationPermissionListAdapter(var enableAll: Boolean, val listener: (feed: Feed) -> Unit) : - ListAdapter(DIFF_CALLBACK), KoinComponent { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationPermissionViewHolder { - val binding = NotificationPermissionLayoutBinding.inflate(LayoutInflater.from(parent.context)) - - return NotificationPermissionViewHolder(binding) - } - - override fun onBindViewHolder(holder: NotificationPermissionViewHolder, position: Int) { - val feed = getItem(position) - - holder.binding.notificationFeedName.text = feed.name - holder.binding.notificationSwitch.isChecked = feed.isNotificationEnabled - - holder.binding.notificationSwitch.isEnabled = enableAll - - holder.itemView.setOnClickListener { if (enableAll) listener(getItem(position)) } - - get() - .load(feed.iconUrl) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .placeholder(R.drawable.ic_rss_feed_grey) - .into(holder.binding.notificationFeedIcon) - } - - override fun onBindViewHolder(holder: NotificationPermissionViewHolder, position: Int, payloads: MutableList) { - if (payloads.isNotEmpty()) { - val feed = payloads.first() as Feed - holder.binding.notificationSwitch.isChecked = feed.isNotificationEnabled - } else onBindViewHolder(holder, position) - } - - inner class NotificationPermissionViewHolder(val binding: NotificationPermissionLayoutBinding) : - RecyclerView.ViewHolder(binding.root) - - companion object { - val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Feed, newItem: Feed): Boolean { - return oldItem.id == newItem.id - } - - override fun areContentsTheSame(oldItem: Feed, newItem: Feed): Boolean { - return oldItem.isNotificationEnabled == newItem.isNotificationEnabled - } - - override fun getChangePayload(oldItem: Feed, newItem: Feed): Any? { - return newItem - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/notifications/NotificationPermissionViewModel.kt b/app/src/main/java/com/readrops/app/notifications/NotificationPermissionViewModel.kt deleted file mode 100644 index d555f8a0..00000000 --- a/app/src/main/java/com/readrops/app/notifications/NotificationPermissionViewModel.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.readrops.app.notifications - -import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel -import com.readrops.db.Database -import com.readrops.db.entities.Feed -import com.readrops.db.entities.account.Account -import io.reactivex.Completable - -class NotificationPermissionViewModel(val database: Database) : ViewModel() { - - var account: Account? = null - - fun getAccount(accountId: Int): LiveData = database.accountDao().selectAsync(accountId) - - fun getFeedsWithNotifPermission(): LiveData> = database.feedDao() - .getFeedsForNotifPermission(account?.id!!) - - fun setAccountNotificationsState(enabled: Boolean): Completable = database.accountDao() - .updateNotificationState(account?.id!!, enabled) - - fun setFeedNotificationState(feed: Feed): Completable = database.feedDao() - .updateFeedNotificationState(feed.id, !feed.isNotificationEnabled) - - fun setAllFeedsNotificationState(enabled: Boolean): Completable = database.feedDao() - .updateAllFeedsNotificationState(account?.id!!, enabled) -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/notifications/sync/SyncResultAnalyser.kt b/app/src/main/java/com/readrops/app/notifications/sync/SyncResultAnalyser.kt deleted file mode 100644 index cf6c24eb..00000000 --- a/app/src/main/java/com/readrops/app/notifications/sync/SyncResultAnalyser.kt +++ /dev/null @@ -1,122 +0,0 @@ -package com.readrops.app.notifications.sync - -import android.content.Context -import androidx.core.content.ContextCompat -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.readrops.app.R -import com.readrops.db.Database -import com.readrops.db.entities.Feed -import com.readrops.db.entities.Item -import com.readrops.db.entities.account.Account -import com.readrops.api.services.SyncResult -import com.readrops.app.utils.GlideRequests -import com.readrops.app.utils.Utils -import org.koin.core.component.KoinComponent -import org.koin.core.component.get - -/** - * Simple class to get synchro notification content (title, content and largeIcon) according to some rules - */ -class SyncResultAnalyser(val context: Context, private val syncResults: Map, val database: Database) : KoinComponent { - - private val notifContent = SyncResultNotifContent() - - fun getSyncNotifContent(): SyncResultNotifContent { - if (newItemsInMultipleAccounts()) { // new items from several accounts - var itemCount = 0 - val feeds = database.feedDao().selectFromIdList(getFeedsIdsForNewItems(syncResults)) - - syncResults.values.forEach { syncResult -> - itemCount += syncResult.items.filter { isFeedNotificationEnabledForItem(feeds, it) }.size - } - - notifContent.title = context.getString(R.string.new_items, itemCount.toString()) - } else { // new items from only one account - val syncResultMap = syncResults.filterValues { it.items.isNotEmpty() } - - if (syncResultMap.values.isNotEmpty()) { - val syncResult = syncResultMap.values.first() - val account = syncResultMap.keys.first() - val feedsIdsForNewItems = getFeedsIdsForNewItems(syncResult) - - notifContent.accountId = account.id - - if (account.isNotificationsEnabled) { - val feeds = database.feedDao().selectFromIdList(feedsIdsForNewItems) - - val items = syncResult.items.filter { isFeedNotificationEnabledForItem(feeds, it) } - val itemCount = items.size - - // new items from several feeds from one account - if (feedsIdsForNewItems.size > 1 && itemCount > 1) { - notifContent.title = account.accountName - notifContent.content = context.getString(R.string.new_items, itemCount.toString()) - notifContent.largeIcon = Utils.getBitmapFromDrawable(ContextCompat.getDrawable(context, account.accountType!!.iconRes)) - } else if (feedsIdsForNewItems.size == 1) // new items from only one feed from one account - oneFeedCase(feedsIdsForNewItems.first(), syncResult.items) - else if (itemCount == 1) - oneFeedCase(items.first().feedId.toLong(), items) - } - } - } - - return notifContent - } - - private fun oneFeedCase(feedId: Long, items: List) { - val feed = database.feedDao().getFeedById(feedId.toInt()) - - if (feed.isNotificationEnabled) { - notifContent.title = feed?.name - - feed?.iconUrl?.let { - val target = get() - .asBitmap() - .load(it) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .submit() - - notifContent.largeIcon = target.get() - } - - if (items.size == 1) { - val item = database.itemDao().selectByRemoteId(items.first().remoteId!!, - items.first().feedId) - notifContent.content = item.title - notifContent.item = item - } else notifContent.content = context.getString(R.string.new_items, items.size.toString()) - } - } - - private fun newItemsInMultipleAccounts(): Boolean { - val itemsNotEmptyByAccount = mutableListOf() - - for ((account, syncResult) in syncResults) { - if (account.isNotificationsEnabled) itemsNotEmptyByAccount += syncResult.items.isNotEmpty() - } - - // return true it there is at least two true booleans in the list - return itemsNotEmptyByAccount.groupingBy { it }.eachCount()[true] ?: 0 > 1 - } - - private fun getFeedsIdsForNewItems(syncResult: SyncResult): List { - val feedsIds = mutableListOf() - - syncResult.items.forEach { - if (it.feedId.toLong() !in feedsIds) - feedsIds += it.feedId.toLong() - } - - return feedsIds - } - - private fun getFeedsIdsForNewItems(syncResults: Map): List { - val feedsIds = mutableListOf() - - syncResults.values.forEach { feedsIds += getFeedsIdsForNewItems(it) } - return feedsIds - } - - private fun isFeedNotificationEnabledForItem(feeds: List, item: Item): Boolean = - feeds.find { it.id == item.feedId }?.isNotificationEnabled!! -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/notifications/sync/SyncResultDebugData.kt b/app/src/main/java/com/readrops/app/notifications/sync/SyncResultDebugData.kt deleted file mode 100644 index 847a2730..00000000 --- a/app/src/main/java/com/readrops/app/notifications/sync/SyncResultDebugData.kt +++ /dev/null @@ -1,100 +0,0 @@ -package com.readrops.app.notifications.sync - -import com.readrops.api.services.SyncResult -import com.readrops.db.Database -import com.readrops.db.entities.Item -import com.readrops.db.entities.account.Account -import com.readrops.db.entities.account.AccountType -import org.jetbrains.annotations.TestOnly -import org.koin.core.component.KoinComponent -import org.koin.core.component.get - -class SyncResultDebugData { - - companion object : KoinComponent { - - @TestOnly - fun oneAccountOneFeedOneItem(): Map { - val database = get() - val account1 = database.accountDao().select(2) - - - val item = database.itemDao().select(5000) - // database.feedDao().updateNotificationState(item.feedId, false).subscribe() - - return mutableMapOf().apply { - put(account1, SyncResult().apply { items = mutableListOf(item) }) - } - } - - @TestOnly - fun oneAccountOneFeedMultipleItems(): Map { - val account1 = Account().apply { - id = 1 - accountType = AccountType.FRESHRSS - isNotificationsEnabled = true - } - - val database = get() - val item = database.itemDao().select(5055) - database.feedDao().updateFeedNotificationState(item.feedId, false).subscribe() - - val item2 = database.itemDao().select(5056) - - return mutableMapOf().apply { - put(account1, SyncResult().apply { items = listOf(item, item2) }) - } - } - - @TestOnly - fun oneAccountMultipleFeeds(): Map { - val account1 = Account().apply { - accountName = "Test account" - id = 1 - accountType = AccountType.FRESHRSS - isNotificationsEnabled = true - } - - val item1 = Item().apply { - id = 1 - title = "oneAccountMultipleFeeds" - feedId = 1 - } - - val item2 = Item().apply { - id = 2 - title = "oneAccountMultipleFeeds" - feedId = 2 - } - - return mutableMapOf().apply { - put(account1, SyncResult().apply { items = mutableListOf(item1, item2) }) - } - } - - fun multipleAccounts(): Map { - val account1 = Account().apply { - id = 1 - accountType = AccountType.FRESHRSS - isNotificationsEnabled = true - } - - val account2 = Account().apply { - id = 2 - accountType = AccountType.LOCAL - isNotificationsEnabled = true - } - - val item = Item().apply { - id = 1 - title = "multipleAccountsCase" - feedId = 90 - } - - return mutableMapOf().apply { - put(account1, SyncResult().apply { items = mutableListOf(item) }) - put(account2, SyncResult().apply { items = mutableListOf(item) }) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/notifications/sync/SyncResultNotifContent.kt b/app/src/main/java/com/readrops/app/notifications/sync/SyncResultNotifContent.kt deleted file mode 100644 index 0e1891cf..00000000 --- a/app/src/main/java/com/readrops/app/notifications/sync/SyncResultNotifContent.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.readrops.app.notifications.sync - -import android.graphics.Bitmap -import com.readrops.db.entities.Item - -class SyncResultNotifContent { - var title: String? = null - var content: String? = null - var largeIcon: Bitmap? = null - var item: Item? = null - var accountId: Int? = null -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/notifications/sync/SyncWorker.kt b/app/src/main/java/com/readrops/app/notifications/sync/SyncWorker.kt deleted file mode 100644 index 896e2cd2..00000000 --- a/app/src/main/java/com/readrops/app/notifications/sync/SyncWorker.kt +++ /dev/null @@ -1,211 +0,0 @@ -package com.readrops.app.notifications.sync - -import android.app.PendingIntent -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.os.Build -import android.util.Log -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import androidx.work.Worker -import androidx.work.WorkerParameters -import com.readrops.api.services.SyncResult -import com.readrops.app.R -import com.readrops.app.ReadropsApp -import com.readrops.app.itemslist.MainActivity -import com.readrops.app.repositories.ARepository -import com.readrops.app.utils.ReadropsKeys -import com.readrops.app.utils.SharedPreferencesManager -import com.readrops.db.Database -import com.readrops.db.entities.Item -import com.readrops.db.entities.account.Account -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers -import org.koin.core.component.KoinComponent -import org.koin.core.component.get -import org.koin.core.parameter.parametersOf - -class SyncWorker(context: Context, parameters: WorkerParameters) : Worker(context, parameters), KoinComponent { - - private var disposable: Disposable? = null - - private val notificationManager = NotificationManagerCompat.from(applicationContext) - private val database = get() - - override fun doWork(): Result { - var result = Result.success() - val syncResults = mutableMapOf() - - try { - val accounts = database.accountDao().selectAll() - - val notificationBuilder = NotificationCompat.Builder(applicationContext, ReadropsApp.SYNC_CHANNEL_ID) - .setContentTitle(applicationContext.getString(R.string.auto_synchro)) - .setProgress(0, 0, true) - .setSmallIcon(R.drawable.ic_notif) - .setOnlyAlertOnce(true) - - accounts.forEach { - notificationBuilder.setContentText(it.accountName) - notificationManager.notify(SYNC_NOTIFICATION_ID, notificationBuilder.build()) - - it.login = SharedPreferencesManager.readString(it.loginKey) - it.password = SharedPreferencesManager.readString(it.passwordKey) - - val repository = get(parameters = { parametersOf(it) }) - - disposable = repository.sync(null, null) - .doOnError { throwable -> - result = Result.failure() - Log.e(TAG, throwable.message!!, throwable) - } - .subscribe() - - if (repository.syncResult != null) syncResults[it] = repository.syncResult - } - } catch (e: Exception) { - Log.e(TAG, e.message!!) - result = Result.failure() - } finally { - notificationManager.cancel(SYNC_NOTIFICATION_ID) - displaySyncResultNotif(syncResults) - - return result - } - } - - override fun onStopped() { - super.onStopped() - - disposable?.dispose() - notificationManager.cancel(SYNC_NOTIFICATION_ID) - } - - private fun displaySyncResultNotif(syncResults: Map) { - val notifContent = SyncResultAnalyser(applicationContext, syncResults, database) - .getSyncNotifContent() - - if (notifContent.title != null) { - val intent = Intent(applicationContext, MainActivity::class.java).apply { - if (notifContent.item != null) { - putExtra(ReadropsKeys.ITEM_ID, notifContent.item?.id) - putExtra(ReadropsKeys.IMAGE_URL, notifContent.item?.imageLink) - - if (notifContent.accountId != null) putExtra(ReadropsKeys.ACCOUNT_ID, notifContent.accountId!!) - } - } - - val intentFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - PendingIntent.FLAG_IMMUTABLE - } else { - PendingIntent.FLAG_IMMUTABLE - } - - val notificationBuilder = NotificationCompat.Builder(applicationContext, ReadropsApp.SYNC_CHANNEL_ID) - .setContentTitle(notifContent.title) - .setContentText(notifContent.content) - .setStyle(NotificationCompat.BigTextStyle().bigText(notifContent.content)) - .setSmallIcon(R.drawable.ic_notif) - .setContentIntent(PendingIntent.getActivity(applicationContext, 0, - intent, intentFlag)) - .setAutoCancel(true) - - notifContent.item?.let { - val feed = database.feedDao().getFeedById(it.feedId) - - notificationBuilder.addAction(buildReadlaterAction(it)) - .addAction(buildMarkAsRead(it)) - .setColor(if (feed.backgroundColor != 0) feed.backgroundColor else feed.textColor) - } - - notifContent.largeIcon?.let { - notificationBuilder.setLargeIcon(it) - } - - notificationManager.notify(SYNC_RESULT_NOTIFICATION_ID, notificationBuilder.build()) - } - - } - - private fun buildReadlaterAction(item: Item): NotificationCompat.Action { - val broadcastIntent = Intent(applicationContext, ReadLaterReceiver::class.java).apply { - putExtra(ReadropsKeys.ITEM_ID, item.id) - } - - val intentFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - PendingIntent.FLAG_IMMUTABLE - } else { - PendingIntent.FLAG_IMMUTABLE - } - - return NotificationCompat.Action.Builder(R.drawable.ic_read_later, applicationContext.getString(R.string.read_later), - PendingIntent.getBroadcast(applicationContext, 0, broadcastIntent, intentFlag)) - .setAllowGeneratedReplies(false) - .build() - } - - private fun buildMarkAsRead(item: Item): NotificationCompat.Action { - val broadcastIntent = Intent(applicationContext, MarkReadReceiver::class.java).apply { - putExtra(ReadropsKeys.ITEM_ID, item.id) - } - - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - NotificationCompat.Action.Builder(R.drawable.ic_read, applicationContext.getString(R.string.read), - PendingIntent.getBroadcast(applicationContext, 0, broadcastIntent, PendingIntent.FLAG_IMMUTABLE)) - .setAllowGeneratedReplies(false) - .build() - } else { - NotificationCompat.Action.Builder(R.drawable.ic_read, applicationContext.getString(R.string.read), - PendingIntent.getBroadcast(applicationContext, 0, broadcastIntent, PendingIntent.FLAG_IMMUTABLE)) - .setAllowGeneratedReplies(false) - .build() - } - - - } - - class MarkReadReceiver : BroadcastReceiver(), KoinComponent { - - override fun onReceive(context: Context?, intent: Intent?) { - val itemId = intent?.getIntExtra(ReadropsKeys.ITEM_ID, 0)!! - - with(get()) { - itemDao().setReadState(itemId, true) - .subscribeOn(Schedulers.io()) - .subscribe() - } - - with(NotificationManagerCompat.from(context!!)) { - cancel(SYNC_RESULT_NOTIFICATION_ID) - } - } - } - - class ReadLaterReceiver : BroadcastReceiver(), KoinComponent { - - override fun onReceive(context: Context?, intent: Intent?) { - val itemId = intent?.getIntExtra(ReadropsKeys.ITEM_ID, 0)!! - - with(get()) { - val item = itemDao().select(itemId) - item.isReadItLater = !item.isReadItLater - - itemDao().setReadItLater(item.isReadItLater, itemId) - .subscribeOn(Schedulers.io()) - .subscribe() - } - - with(NotificationManagerCompat.from(context!!)) { - cancel(SYNC_RESULT_NOTIFICATION_ID) - } - } - - } - - companion object { - val TAG = SyncWorker::class.java.simpleName - private const val SYNC_NOTIFICATION_ID = 2 - const val SYNC_RESULT_NOTIFICATION_ID = 3 - } -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/repositories/ARepository.java b/app/src/main/java/com/readrops/app/repositories/ARepository.java deleted file mode 100644 index ac234c8f..00000000 --- a/app/src/main/java/com/readrops/app/repositories/ARepository.java +++ /dev/null @@ -1,207 +0,0 @@ -package com.readrops.app.repositories; - -import static com.readrops.app.utils.ReadropsKeys.FEEDS; - -import android.content.Context; -import android.content.Intent; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.readrops.api.services.Credentials; -import com.readrops.api.services.SyncResult; -import com.readrops.api.utils.AuthInterceptor; -import com.readrops.app.addfeed.FeedInsertionResult; -import com.readrops.app.addfeed.ParsingResult; -import com.readrops.app.utils.feedscolors.FeedColorsKt; -import com.readrops.app.utils.feedscolors.FeedsColorsIntentService; -import com.readrops.db.Database; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.Folder; -import com.readrops.db.entities.Item; -import com.readrops.db.entities.ItemState; -import com.readrops.db.entities.account.Account; - -import org.koin.java.KoinJavaComponent; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import io.reactivex.Completable; -import io.reactivex.Single; - -public abstract class ARepository { - - protected Context context; - protected Database database; - protected Account account; - - protected SyncResult syncResult; - - protected ARepository(Database database, @NonNull Context context, @Nullable Account account) { - this.context = context; - this.database = database; - this.account = account; - - setCredentials(account); - } - - protected void setCredentials(@Nullable Account account) { - KoinJavaComponent.get(AuthInterceptor.class) - .setCredentials(account != null && !account.isLocal() ? Credentials.toCredentials(account) : null); - } - - public abstract Completable login(Account account, boolean insert); - - public abstract Completable sync(@Nullable List feeds, @Nullable FeedUpdate update); - - public abstract Single> addFeeds(List results); - - public Completable insertOPMLFoldersAndFeeds(Map> foldersAndFeeds) { - List completableList = new ArrayList<>(); - - for (Map.Entry> entry : foldersAndFeeds.entrySet()) { - Folder folder = entry.getKey(); - folder.setAccountId(account.getId()); - - Completable completable = Single.create(emitter -> { - Folder dbFolder = database.folderDao().getFolderByName(folder.getName(), account.getId()); - - if (dbFolder != null) - emitter.onSuccess(dbFolder.getId()); - else - emitter.onSuccess((int) database.folderDao().compatInsert(folder)); - }).flatMap(folderId -> { - List feeds = entry.getValue(); - for (Feed feed : feeds) { - feed.setFolderId(folderId); - } - - List parsingResults = ParsingResult.toParsingResults(feeds); - return addFeeds(parsingResults); - }).flatMapCompletable(feedInsertionResults -> Completable.complete()); - - completableList.add(completable); - } - - return Completable.concat(completableList); - } - - public Completable updateFeed(Feed feed) { - return Completable.create(emitter -> { - database.feedDao().updateFeedFields(feed.getId(), feed.getName(), feed.getUrl(), feed.getFolderId()); - emitter.onComplete(); - }); - } - - public Completable deleteFeed(Feed feed) { - return database.feedDao().delete(feed); - } - - public Single addFolder(Folder folder) { - return database.folderDao().insert(folder); - } - - public Completable updateFolder(Folder folder) { - return database.folderDao().update(folder); - } - - public Completable deleteFolder(Folder folder) { - return database.folderDao().delete(folder); - } - - public Completable setItemReadState(Item item) { - if (account.getConfig().getUseSeparateState()) { - return database.itemStateChangesDao().upsertItemReadStateChange(item, account.getId(), true) - .andThen(database.itemStateDao().upsertItemReadState(new ItemState(0, item.isRead(), - item.isStarred(), item.getRemoteId(), account.getId()))); - } else if (account.isLocal()) { - return database.itemDao().setReadState(item.getId(), item.isRead()); - } else { // nextcloud case - return database.itemStateChangesDao().upsertItemReadStateChange(item, account.getId(), false) - .andThen(database.itemDao().setReadState(item.getId(), item.isRead())); - } - - } - - public Completable setAllItemsReadState(boolean read) { - if (account.isLocal()) { // TODO see if it's possible to implement for others accounts - return database.itemDao().setAllItemsReadState(read ? 1 : 0, account.getId()); - } else { - return Completable.complete(); - } - } - - public Completable setAllFeedItemsReadState(int feedId, boolean read) { - if (account.isLocal()) { - return database.itemDao().setAllFeedItemsReadState(feedId, read ? 1 : 0); - } else { - return Completable.complete(); - } - } - - public Completable setItemStarState(Item item) { - if (account.getConfig().getUseSeparateState()) { - return database.itemStateChangesDao().upsertItemStarStateChange(item, account.getId(), true) - .andThen(database.itemStateDao().upsertItemStarState(new ItemState(0, item.isRead(), - item.isStarred(), item.getRemoteId(), account.getId()))); - } else if (account.isLocal()) { - return database.itemDao().setStarState(item.getId(), item.isRead()); - } else { // nextcloud case - return database.itemStateChangesDao().upsertItemStarStateChange(item, account.getId(), false) - .andThen(database.itemDao().setStarState(item.getId(), item.isStarred())); - } - } - - public Single getFeedCount(int accountId) { - return database.feedDao().getFeedCount(accountId); - } - - public Single>> getFoldersWithFeeds() { - return Single.create(emitter -> { - List folders = database.folderDao().getFolders(account.getId()); - Map> foldersWithFeeds = new TreeMap<>(Comparator.nullsLast(Folder::compareTo)); - - for (Folder folder : folders) { - List feeds = database.feedDao().getFeedsByFolder(folder.getId()); - - for (Feed feed : feeds) { - int unreadCount = database.itemDao().getUnreadCount(feed.getId()); - feed.setUnreadCount(unreadCount); - } - - foldersWithFeeds.put(folder, feeds); - } - - // feeds without folder - List feedsWithoutFolder = database.feedDao().getFeedsWithoutFolder(account.getId()); - for (Feed feed : feedsWithoutFolder) { - feed.setUnreadCount(database.itemDao().getUnreadCount(feed.getId())); - } - - foldersWithFeeds.put(null, feedsWithoutFolder); - - emitter.onSuccess(foldersWithFeeds); - }); - } - - protected void setFeedColors(Feed feed) { - FeedColorsKt.setFeedColors(feed); - database.feedDao().updateColors(feed.getId(), - feed.getTextColor(), feed.getBackgroundColor()); - } - - protected void setFeedsColors(List feeds) { - Intent intent = new Intent(context, FeedsColorsIntentService.class); - intent.putParcelableArrayListExtra(FEEDS, new ArrayList<>(feeds)); - - context.startService(intent); - } - - public SyncResult getSyncResult() { - return syncResult; - } -} diff --git a/app/src/main/java/com/readrops/app/repositories/FeedUpdate.kt b/app/src/main/java/com/readrops/app/repositories/FeedUpdate.kt deleted file mode 100644 index 8280f116..00000000 --- a/app/src/main/java/com/readrops/app/repositories/FeedUpdate.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.readrops.app.repositories - -import com.readrops.db.entities.Feed - -interface FeedUpdate { - - fun onNext(feed: Feed) - -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/repositories/FreshRSSRepository.java b/app/src/main/java/com/readrops/app/repositories/FreshRSSRepository.java deleted file mode 100644 index 945554c1..00000000 --- a/app/src/main/java/com/readrops/app/repositories/FreshRSSRepository.java +++ /dev/null @@ -1,301 +0,0 @@ -package com.readrops.app.repositories; - -import android.content.Context; -import android.util.Log; -import android.util.TimingLogger; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.readrops.api.services.SyncType; -import com.readrops.api.services.freshrss.FreshRSSDataSource; -import com.readrops.api.services.freshrss.FreshRSSSyncData; -import com.readrops.app.addfeed.FeedInsertionResult; -import com.readrops.app.addfeed.ParsingResult; -import com.readrops.app.utils.Utils; -import com.readrops.db.Database; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.Folder; -import com.readrops.db.entities.Item; -import com.readrops.db.entities.ItemState; -import com.readrops.db.entities.account.Account; -import com.readrops.db.pojo.ItemReadStarState; - -import org.joda.time.DateTime; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import io.reactivex.Completable; -import io.reactivex.Single; - -public class FreshRSSRepository extends ARepository { - - private static final String TAG = FreshRSSRepository.class.getSimpleName(); - - private final FreshRSSDataSource dataSource; - - public FreshRSSRepository(FreshRSSDataSource dataSource, Database database, @NonNull Context context, @Nullable Account account) { - super(database, context, account); - - this.dataSource = dataSource; - } - - @Override - public Completable login(Account account, boolean insert) { - setCredentials(account); - - return dataSource.login(account.getLogin(), account.getPassword()) - .flatMap(token -> { - account.setToken(token); - setCredentials(account); - - return dataSource.getWriteToken(); - }) - .flatMap(writeToken -> { - account.setWriteToken(writeToken); - - return dataSource.getUserInfo(); - }) - .flatMapCompletable(userInfo -> { - account.setDisplayedName(userInfo.getUserName()); - - if (insert) { - return database.accountDao().insert(account) - .flatMapCompletable(id -> { - account.setId(id.intValue()); - - return Completable.complete(); - }); - } - - return Completable.complete(); - }); - } - - @Override - public Completable sync(@Nullable List feeds, @Nullable FeedUpdate update) { - FreshRSSSyncData syncData = new FreshRSSSyncData(); - SyncType syncType; - - if (account.getLastModified() != 0) { - syncType = SyncType.CLASSIC_SYNC; - syncData.setLastModified(account.getLastModified()); - } else - syncType = SyncType.INITIAL_SYNC; - - long newLastModified = DateTime.now().getMillis() / 1000L; - TimingLogger logger = new TimingLogger(TAG, "FreshRSS sync timer"); - - return Single.create(emitter -> { - List itemStateChanges = database - .itemStateChangesDao() - .getItemStateChanges(account.getId()); - - syncData.setReadIds(itemStateChanges.stream() - .filter(it -> it.getReadChange() && it.getRead()) - .map(ItemReadStarState::getRemoteId) - .collect(Collectors.toList())); - - syncData.setUnreadIds(itemStateChanges.stream() - .filter(it -> it.getReadChange() && !it.getRead()) - .map(ItemReadStarState::getRemoteId) - .collect(Collectors.toList())); - - syncData.setStarredIds(itemStateChanges.stream() - .filter(it -> it.getStarChange() && it.getStarred()) - .map(ItemReadStarState::getRemoteId) - .collect(Collectors.toList())); - - syncData.setUnstarredIds(itemStateChanges.stream() - .filter(it -> it.getStarChange() && !it.getStarred()) - .map(ItemReadStarState::getRemoteId) - .collect(Collectors.toList())); - - emitter.onSuccess(syncData); - }).flatMap(syncData1 -> dataSource.sync(syncType, syncData1, account.getWriteToken())) - .flatMapCompletable(syncResult -> { - logger.addSplit("server queries"); - - insertFolders(syncResult.getFolders()); - logger.addSplit("folders insertion"); - insertFeeds(syncResult.getFeeds()); - logger.addSplit("feeds insertion"); - - insertItems(syncResult.getItems(), false); - logger.addSplit("items insertion"); - - insertItems(syncResult.getStarredItems(), true); - logger.addSplit("starred items insertion"); - - insertItemsIds(syncResult.getUnreadIds(), syncResult.getReadIds(), syncResult.getStarredIds()); - logger.addSplit("insert and update items ids"); - - account.setLastModified(newLastModified); - database.accountDao().updateLastModified(account.getId(), newLastModified); - - database.itemStateChangesDao().resetStateChanges(account.getId()); - - logger.dumpToLog(); - - this.syncResult = syncResult; - - return Completable.complete(); - }); - } - - @Override - public Single> addFeeds(List results) { - List completableList = new ArrayList<>(); - List insertionResults = new ArrayList<>(); - - for (ParsingResult result : results) { - completableList.add(dataSource.createFeed(account.getWriteToken(), result.getUrl()) - .doOnComplete(() -> { - FeedInsertionResult feedInsertionResult = new FeedInsertionResult(); - feedInsertionResult.setParsingResult(result); - insertionResults.add(feedInsertionResult); - }).onErrorResumeNext(throwable -> { - Log.d(TAG, throwable.getMessage()); - - FeedInsertionResult feedInsertionResult = new FeedInsertionResult(); - - feedInsertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.ERROR); - feedInsertionResult.setParsingResult(result); - insertionResults.add(feedInsertionResult); - - return Completable.complete(); - })); - } - - return Completable.concat(completableList) - .andThen(Single.just(insertionResults)); - } - - @Override - public Completable updateFeed(Feed feed) { - return Single.create(emitter -> { - Folder folder = feed.getFolderId() == null ? null : database.folderDao().select(feed.getFolderId()); - emitter.onSuccess(folder); - - }).flatMapCompletable(folder -> dataSource.updateFeed(account.getWriteToken(), - feed.getUrl(), feed.getName(), folder == null ? null : folder.getRemoteId()) - .andThen(super.updateFeed(feed))); - } - - @Override - public Completable deleteFeed(Feed feed) { - return dataSource.deleteFeed(account.getWriteToken(), feed.getUrl()) - .andThen(super.deleteFeed(feed)); - } - - @Override - public Single addFolder(Folder folder) { - return dataSource.createFolder(account.getWriteToken(), folder.getName()) - .andThen(super.addFolder(folder)); - } - - @Override - public Completable updateFolder(Folder folder) { - return dataSource.updateFolder(account.getWriteToken(), folder.getRemoteId(), folder.getName()) - .andThen(Completable.create(emitter -> { - folder.setRemoteId("user/-/label/" + folder.getName()); - emitter.onComplete(); - })) - .andThen(super.updateFolder(folder)); - } - - @Override - public Completable deleteFolder(Folder folder) { - return dataSource.deleteFolder(account.getWriteToken(), folder.getRemoteId()) - .andThen(super.deleteFolder(folder)); - } - - private void insertFeeds(List freshRSSFeeds) { - freshRSSFeeds.stream().forEach(feed -> feed.setAccountId(account.getId())); - - List insertedFeedsIds = database.feedDao().feedsUpsert(freshRSSFeeds, account); - - if (!insertedFeedsIds.isEmpty()) { - setFeedsColors(database.feedDao().selectFromIdList(insertedFeedsIds)); - } - - } - - private void insertFolders(List freshRSSFolders) { - freshRSSFolders.stream().forEach(folder -> folder.setAccountId(account.getId())); - - database.folderDao().foldersUpsert(freshRSSFolders, account); - } - - private void insertItems(List items, boolean starredItems) { - List itemsToInsert = new ArrayList<>(); - Map itemsFeedsIds = new HashMap<>(); - - for (Item item : items) { - Integer feedId; - if (itemsFeedsIds.containsKey(item.getFeedRemoteId())) { - feedId = itemsFeedsIds.get(item.getFeedRemoteId()); - } else { - feedId = database.feedDao().getFeedIdByRemoteId(item.getFeedRemoteId(), account.getId()); - itemsFeedsIds.put(item.getFeedRemoteId(), feedId); - } - - item.setFeedId(feedId); - if (item.getText() != null) { - item.setReadTime(Utils.readTimeFromString(item.getText())); - } - - - // workaround to avoid inserting starred items coming from the main item call - // as the API exclusion filter doesn't seem to work - if (!starredItems) { - if (!item.isStarred()) { - itemsToInsert.add(item); - } - } else { - itemsToInsert.add(item); - } - } - - if (!itemsToInsert.isEmpty()) { - Collections.sort(itemsToInsert, Item::compareTo); - database.itemDao().insert(itemsToInsert); - } - } - - private void insertItemsIds(List unreadIds, List readIds, List starredIds) { - database.itemStateDao().deleteItemsStates(account.getId()); - - database.itemStateDao().insertItemStates(unreadIds.stream().map(id -> { - boolean starred = starredIds.stream().filter(starredId -> starredId.equals(id)).count() == 1; - if (starred) { - starredIds.remove(id); - } - - return new ItemState(0, false, starred, id, account.getId()); - } - ).collect(Collectors.toList())); - - database.itemStateDao().insertItemStates(readIds.stream().map(id -> { - boolean starred = starredIds.stream().filter(starredId -> starredId.equals(id)).count() == 1; - if (starred) { - starredIds.remove(id); - } - - return new ItemState(0, true, starred, id, account.getId()); - } - ).collect(Collectors.toList())); - - // insert starred items ids which are read - if (!starredIds.isEmpty()) { - database.itemStateDao().insertItemStates(starredIds.stream().map(id -> - new ItemState(0, true, true, id, account.getId())) - .collect(Collectors.toList())); - } - } -} diff --git a/app/src/main/java/com/readrops/app/repositories/LocalFeedRepository.java b/app/src/main/java/com/readrops/app/repositories/LocalFeedRepository.java deleted file mode 100644 index 62a94779..00000000 --- a/app/src/main/java/com/readrops/app/repositories/LocalFeedRepository.java +++ /dev/null @@ -1,188 +0,0 @@ -package com.readrops.app.repositories; - -import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.readrops.api.localfeed.LocalRSSDataSource; -import com.readrops.api.services.SyncResult; -import com.readrops.api.utils.ApiUtils; -import com.readrops.api.utils.exceptions.ParseException; -import com.readrops.api.utils.exceptions.UnknownFormatException; -import com.readrops.app.addfeed.FeedInsertionResult; -import com.readrops.app.addfeed.ParsingResult; -import com.readrops.app.utils.SharedPreferencesManager; -import com.readrops.app.utils.Utils; -import com.readrops.db.Database; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.Item; -import com.readrops.db.entities.account.Account; - -import org.jsoup.Jsoup; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import io.reactivex.Completable; -import io.reactivex.Single; -import kotlin.Pair; -import okhttp3.Headers; - -public class LocalFeedRepository extends ARepository { - - private static final String TAG = LocalFeedRepository.class.getSimpleName(); - - private LocalRSSDataSource dataSource; - - public LocalFeedRepository(LocalRSSDataSource dataSource, Database database, @NonNull Context context, @Nullable Account account) { - super(database, context, account); - - syncResult = new SyncResult(); - this.dataSource = dataSource; - } - - @Override - public Completable login(Account account, boolean insert) { - return null; - } - - @Override - public Completable sync(@Nullable List feeds, FeedUpdate update) { - return Completable.create(emitter -> { - List feedList; - - if (feeds == null || feeds.isEmpty()) { - feedList = database.feedDao().getFeeds(account.getId()); - } else { - feedList = feeds; - } - - for (Feed feed : feedList) { - Handler mainHandler = new Handler(Looper.getMainLooper()); - mainHandler.post(() -> update.onNext(feed)); - - try { - Headers.Builder headers = new Headers.Builder(); - if (feed.getEtag() != null) { - headers.add(ApiUtils.IF_NONE_MATCH_HEADER, feed.getEtag()); - } - if (feed.getLastModified() != null) { - headers.add(ApiUtils.IF_MODIFIED_HEADER, feed.getLastModified()); - } - - Pair> pair = dataSource.queryRSSResource(feed.getUrl(), headers.build()); - - if (pair != null) { - insertNewItems(feed, pair.getSecond()); - } - } catch (Exception e) { - Log.d(TAG, "sync: " + e.getMessage()); - } - } - - emitter.onComplete(); - }); - } - - @Override - public Single> addFeeds(List results) { - return Single.create(emitter -> { - List insertionResults = new ArrayList<>(); - - for (ParsingResult parsingResult : results) { - FeedInsertionResult insertionResult = new FeedInsertionResult(); - - try { - Pair> pair = dataSource.queryRSSResource(parsingResult.getUrl(), - null); - Feed feed = insertFeed(pair.getFirst(), parsingResult); - - if (feed != null) { - insertionResult.setFeed(feed); - } - } catch (ParseException e) { - Log.d(TAG, "addFeeds: " + e.getMessage()); - insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.PARSE_ERROR); - } catch (UnknownFormatException e) { - Log.d(TAG, "addFeeds: " + e.getMessage()); - insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.FORMAT_ERROR); - } catch (IOException e) { - Log.d(TAG, "addFeeds: " + e.getMessage()); - insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.NETWORK_ERROR); - } catch (Exception e) { - Log.d(TAG, "addFeeds: " + e.getMessage()); - insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.UNKNOWN_ERROR); - } finally { - insertionResult.setParsingResult(parsingResult); - insertionResults.add(insertionResult); - } - } - - emitter.onSuccess(insertionResults); - }); - } - - @SuppressWarnings("SimplifyStreamApiCallChains") - private void insertNewItems(Feed feed, List items) { - database.feedDao().updateHeaders(feed.getEtag(), feed.getLastModified(), feed.getId()); - - Collections.sort(items, Item::compareTo); - - int maxItems = Integer.parseInt(SharedPreferencesManager.readString( - SharedPreferencesManager.SharedPrefKey.ITEMS_TO_PARSE_MAX_NB)); - if (maxItems > 0 && items.size() > maxItems) { - items = items.subList(items.size() - maxItems, items.size()); - } - - items.stream().forEach(item -> item.setFeedId(feed.getId())); - insertItems(items, feed); - } - - private Feed insertFeed(Feed feed, ParsingResult parsingResult) { - feed.setFolderId(parsingResult.getFolderId()); - - if (database.feedDao().feedExists(feed.getUrl(), account.getId())) { - return null; // feed already inserted - } - - setFeedColors(feed); - feed.setAccountId(account.getId()); - - // we need empty headers to query the feed just after, without any 304 result - feed.setEtag(null); - feed.setLastModified(null); - - feed.setId((int) (database.feedDao().compatInsert(feed))); - return feed; - } - - private void insertItems(Collection items, Feed feed) { - List itemsToInsert = new ArrayList<>(); - - for (Item dbItem : items) { - if (!database.itemDao().itemExists(dbItem.getGuid(), feed.getAccountId())) { - if (dbItem.getDescription() != null) { - dbItem.setCleanDescription(Jsoup.parse(dbItem.getDescription()).text()); - } - - if (dbItem.getContent() != null) { - dbItem.setReadTime(Utils.readTimeFromString(dbItem.getContent())); - } else if (dbItem.getDescription() != null) { - dbItem.setReadTime(Utils.readTimeFromString(dbItem.getCleanDescription())); - } - - itemsToInsert.add(dbItem); - } - } - - syncResult.getItems().addAll(itemsToInsert); - database.itemDao().insert(itemsToInsert); - } -} diff --git a/app/src/main/java/com/readrops/app/repositories/NextNewsRepository.java b/app/src/main/java/com/readrops/app/repositories/NextNewsRepository.java deleted file mode 100644 index 436adf4b..00000000 --- a/app/src/main/java/com/readrops/app/repositories/NextNewsRepository.java +++ /dev/null @@ -1,352 +0,0 @@ -package com.readrops.app.repositories; - -import android.content.Context; -import android.database.sqlite.SQLiteConstraintException; -import android.util.Log; -import android.util.TimingLogger; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.readrops.api.services.SyncResult; -import com.readrops.api.services.SyncType; -import com.readrops.api.services.nextcloudnews.NextNewsDataSource; -import com.readrops.api.services.nextcloudnews.NextcloudNewsSyncData; -import com.readrops.api.utils.exceptions.UnknownFormatException; -import com.readrops.app.addfeed.FeedInsertionResult; -import com.readrops.app.addfeed.ParsingResult; -import com.readrops.app.utils.Utils; -import com.readrops.db.Database; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.Folder; -import com.readrops.db.entities.Item; -import com.readrops.db.entities.account.Account; -import com.readrops.db.pojo.ItemReadStarState; - -import org.joda.time.LocalDateTime; -import org.koin.java.KoinJavaComponent; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import io.reactivex.Completable; -import io.reactivex.Single; -import okhttp3.OkHttpClient; - -public class NextNewsRepository extends ARepository { - - private static final String TAG = NextNewsRepository.class.getSimpleName(); - - private final NextNewsDataSource dataSource; - - public NextNewsRepository(NextNewsDataSource dataSource, Database database, @NonNull Context context, @Nullable Account account) { - super(database, context, account); - - this.dataSource = dataSource; - } - - @Override - public Completable login(Account account, boolean insert) { - setCredentials(account); - return Single.create(emitter -> { - OkHttpClient httpClient = KoinJavaComponent.get(OkHttpClient.class); - - String displayName = dataSource.login(httpClient, account); - emitter.onSuccess(displayName); - }).flatMapCompletable(displayName -> { - account.setDisplayedName(displayName); - account.setCurrentAccount(true); - - if (insert) { - return database.accountDao().insert(account) - .flatMapCompletable(id -> { - account.setId(id.intValue()); - return Completable.complete(); - }); - } - - return Completable.complete(); - }); - } - - @Override - public Completable sync(@Nullable List feeds, @Nullable FeedUpdate update) { - setCredentials(account); - return Completable.create(emitter -> { - try { - long lastModified = LocalDateTime.now().toDateTime().getMillis(); - SyncType syncType; - - if (account.getLastModified() != 0) { - syncType = SyncType.CLASSIC_SYNC; - } else { - syncType = SyncType.INITIAL_SYNC; - } - - NextcloudNewsSyncData syncData = new NextcloudNewsSyncData(); - - /*if (syncType == SyncType.CLASSIC_SYNC) { - syncData.setLastModified(account.getLastModified() / 1000L); - - List itemStateChanges = database - .itemStateChangesDao() - .getNextcloudNewsStateChanges(account.getId()); - - syncData.setReadIds(itemStateChanges.stream() - .filter(it -> it.getReadChange() && it.getRead()) - .map(ItemReadStarState::getRemoteId) - .collect(Collectors.toList())); - - syncData.setUnreadIds(itemStateChanges.stream() - .filter(it -> it.getReadChange() && !it.getRead()) - .map(ItemReadStarState::getRemoteId) - .collect(Collectors.toList())); - - List starredItemsIds = itemStateChanges.stream() - .filter(it -> it.getStarChange() && it.getStarred()) - .map(ItemReadStarState::getRemoteId) - .collect(Collectors.toList()); - - if (!starredItemsIds.isEmpty()) { - syncData.setStarredIds(database.itemDao().getStarChanges(starredItemsIds, account.getId())); - } - - List unstarredItemsIds = itemStateChanges.stream() - .filter(it -> it.getStarChange() && !it.getStarred()) - .map(ItemReadStarState::getRemoteId) - .collect(Collectors.toList()); - - if (!unstarredItemsIds.isEmpty()) { - syncData.setUnstarredIds(database.itemDao().getStarChanges(unstarredItemsIds, account.getId())); - } - - }*/ - - TimingLogger timings = new TimingLogger(TAG, "nextcloud news " + syncType.name().toLowerCase()); - SyncResult result = dataSource.sync(syncType, syncData); - timings.addSplit("server queries"); - - if (!result.isError()) { - syncResult = new SyncResult(); - - insertFolders(result.getFolders()); - timings.addSplit("insert folders"); - - insertFeeds(result.getFeeds(), false); - timings.addSplit("insert feeds"); - - boolean initialSync = syncType == SyncType.INITIAL_SYNC; - insertItems(result.getItems(), initialSync); - timings.addSplit("insert items"); - - insertItems(result.getStarredItems(), initialSync); - timings.dumpToLog(); - - account.setLastModified(lastModified); - database.accountDao().updateLastModified(account.getId(), lastModified); - - database.itemStateChangesDao().resetStateChanges(account.getId()); - - emitter.onComplete(); - } else { - emitter.onError(new Throwable()); - } - - } catch (Exception e) { - Log.d(TAG, "sync: " + e.getMessage()); - emitter.onError(e); - } - }); - } - - @Override - public Single> addFeeds(List results) { - setCredentials(account); - return Single.create(emitter -> { - List feedInsertionResults = new ArrayList<>(); - - for (ParsingResult result : results) { - FeedInsertionResult insertionResult = new FeedInsertionResult(); - - try { - List nextNewsFeeds = dataSource.createFeed(result.getUrl(), 0); - - if (nextNewsFeeds != null) { - List newFeeds = insertFeeds(nextNewsFeeds, true); - // there is always only one object in the list, see nextcloud news dataSource doc - insertionResult.setFeed(newFeeds.get(0)); - } else - insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.UNKNOWN_ERROR); - - insertionResult.setParsingResult(result); - } catch (Exception e) { - if (e instanceof IOException) - insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.NETWORK_ERROR); - else if (e instanceof UnknownFormatException) - insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.FORMAT_ERROR); - else if (e instanceof SQLiteConstraintException) - insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.DB_ERROR); - else - insertionResult.setInsertionError(FeedInsertionResult.FeedInsertionError.UNKNOWN_ERROR); - } - - feedInsertionResults.add(insertionResult); - } - - emitter.onSuccess(feedInsertionResults); - }); - } - - @Override - public Completable updateFeed(Feed feed) { - setCredentials(account); - return Completable.create(emitter -> { - Folder folder = feed.getFolderId() == null ? null : database.folderDao().select(feed.getFolderId()); - - if (folder != null) - feed.setRemoteFolderId(folder.getRemoteId()); - else - feed.setRemoteFolderId(String.valueOf(0)); // 0 for no folder - - try { - if (dataSource.renameFeed(feed) && dataSource.changeFeedFolder(feed)) { - emitter.onComplete(); - } else - emitter.onError(new Exception("Unknown error when updating feed")); - } catch (Exception e) { - emitter.onError(e); - } - }).andThen(super.updateFeed(feed)); - } - - @Override - public Completable deleteFeed(Feed feed) { - setCredentials(account); - return Completable.create(emitter -> { - try { - if (dataSource.deleteFeed(Integer.parseInt(feed.getRemoteId()))) { - emitter.onComplete(); - } else - emitter.onError(new Exception("Unknown error")); - } catch (Exception e) { - emitter.onError(e); - } - - emitter.onComplete(); - }).andThen(super.deleteFeed(feed)); - } - - @Override - public Single addFolder(Folder folder) { - setCredentials(account); - return Single.create(emitter -> { - try { - List folders = dataSource.createFolder(folder); - - if (folders != null) { - Folder nextNewsFolder = folders.get(0); // always only one item returned by the server, see doc - folder.setRemoteId(nextNewsFolder.getRemoteId()); - - emitter.onSuccess(folder); - } else - emitter.onError(new Exception("Unknown error")); - } catch (Exception e) { - emitter.onError(e); - } - }).flatMap(folder1 -> database.folderDao().insert(folder)); - } - - @Override - public Completable updateFolder(Folder folder) { - setCredentials(account); - return Completable.create(emitter -> { - try { - if (dataSource.renameFolder(folder)) { - emitter.onComplete(); - } else - emitter.onError(new Exception("Unknown error")); - - } catch (Exception e) { - emitter.onError(e); - } - - emitter.onComplete(); - }).andThen(super.updateFolder(folder)); - } - - @Override - public Completable deleteFolder(Folder folder) { - setCredentials(account); - return Completable.create(emitter -> { - try { - if (dataSource.deleteFolder(folder)) { - emitter.onComplete(); - } else - emitter.onError(new Exception("Unknown error")); - - } catch (Exception e) { - emitter.onError(e); - } - - emitter.onComplete(); - }).andThen(super.deleteFolder(folder)); - } - - private List insertFeeds(List nextNewsFeeds, boolean newFeeds) { - for (Feed nextNewsFeed : nextNewsFeeds) { - nextNewsFeed.setAccountId(account.getId()); - } - - List insertedFeedsIds; - if (newFeeds) { - insertedFeedsIds = database.feedDao().insert(nextNewsFeeds); - } else { - insertedFeedsIds = database.feedDao().feedsUpsert(nextNewsFeeds, account); - } - - List insertedFeeds = new ArrayList<>(); - if (!insertedFeedsIds.isEmpty()) { - insertedFeeds.addAll(database.feedDao().selectFromIdList(insertedFeedsIds)); - setFeedsColors(insertedFeeds); - } - - return insertedFeeds; - } - - private void insertFolders(List nextNewsFolders) { - for (Folder folder : nextNewsFolders) { - folder.setAccountId(account.getId()); - } - - database.folderDao().foldersUpsert(nextNewsFolders, account); - } - - private void insertItems(List items, boolean initialSync) { - List itemsToInsert = new ArrayList<>(); - - for (Item item : items) { - int feedId = database.feedDao().getFeedIdByRemoteId(item.getFeedRemoteId(), account.getId()); - - //if the item already exists, update only its read state - if (!initialSync && feedId > 0 && database.itemDao().remoteItemExists(String.valueOf(item.getRemoteId()), feedId)) { - database.itemDao().setReadAndStarState(item.getRemoteId(), item.isRead(), item.isStarred()); - continue; - } - - item.setFeedId(feedId); - item.setReadTime(Utils.readTimeFromString(item.getContent())); - - itemsToInsert.add(item); - } - - if (!itemsToInsert.isEmpty()) { - syncResult.setItems(itemsToInsert); - - Collections.sort(itemsToInsert, Item::compareTo); - database.itemDao().insert(itemsToInsert); - } - } -} diff --git a/app/src/main/java/com/readrops/app/settings/AccountSettingsFragment.java b/app/src/main/java/com/readrops/app/settings/AccountSettingsFragment.java deleted file mode 100644 index d604eb3d..00000000 --- a/app/src/main/java/com/readrops/app/settings/AccountSettingsFragment.java +++ /dev/null @@ -1,325 +0,0 @@ -package com.readrops.app.settings; - - -import static android.app.Activity.RESULT_OK; -import static com.readrops.app.utils.OPMLHelper.OPEN_OPML_FILE_REQUEST; -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT; -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT_ID; -import static com.readrops.app.utils.ReadropsKeys.EDIT_ACCOUNT; - -import android.Manifest; -import android.app.Notification; -import android.app.PendingIntent; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.provider.Settings; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; -import androidx.fragment.app.Fragment; -import androidx.preference.Preference; -import androidx.preference.PreferenceFragmentCompat; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.readrops.api.opml.OPMLParser; -import com.readrops.app.R; -import com.readrops.app.ReadropsApp; -import com.readrops.app.account.AccountViewModel; -import com.readrops.app.account.AddAccountActivity; -import com.readrops.app.feedsfolders.ManageFeedsFoldersActivity; -import com.readrops.app.notifications.NotificationPermissionActivity; -import com.readrops.app.utils.FileUtils; -import com.readrops.app.utils.OPMLHelper; -import com.readrops.app.utils.PermissionManager; -import com.readrops.app.utils.SharedPreferencesManager; -import com.readrops.app.utils.Utils; -import com.readrops.db.entities.Feed; -import com.readrops.db.entities.Folder; -import com.readrops.db.entities.account.Account; -import com.readrops.db.entities.account.AccountType; - -import org.koin.android.compat.ViewModelCompat; - -import java.io.FileNotFoundException; -import java.util.List; -import java.util.Map; - -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.observers.DisposableCompletableObserver; -import io.reactivex.schedulers.Schedulers; -import kotlin.Unit; - -/** - * A simple {@link Fragment} subclass. - */ -public class AccountSettingsFragment extends PreferenceFragmentCompat { - - private static final String TAG = AccountSettingsFragment.class.getSimpleName(); - - private static final int WRITE_EXTERNAL_STORAGE_REQUEST = 1; - - private Account account; - private AccountViewModel viewModel; - - public AccountSettingsFragment() { - - } - - public static AccountSettingsFragment newInstance(Account account) { - AccountSettingsFragment fragment = new AccountSettingsFragment(); - Bundle args = new Bundle(); - - args.putParcelable(ACCOUNT, account); - fragment.setArguments(args); - - return fragment; - } - - @SuppressWarnings("ConstantConditions") - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - addPreferencesFromResource(R.xml.acount_preferences); - - account = getArguments().getParcelable(ACCOUNT); - - Preference feedsFoldersPref = findPreference("feeds_folders_key"); - Preference credentialsPref = findPreference("credentials_key"); - Preference deleteAccountPref = findPreference("delete_account_key"); - Preference opmlPref = findPreference("opml_import_export"); - Preference notificationPref = findPreference("notifications"); - - if (account.is(AccountType.LOCAL)) - credentialsPref.setVisible(false); - - if (!account.is(AccountType.LOCAL)) - opmlPref.setVisible(false); - - feedsFoldersPref.setOnPreferenceClickListener(preference -> { - Intent intent = new Intent(getContext(), ManageFeedsFoldersActivity.class); - intent.putExtra(ACCOUNT, account); - startActivity(intent); - - return true; - }); - - credentialsPref.setOnPreferenceClickListener(preference -> { - if (!account.isLocal()) { - Intent intent = new Intent(getContext(), AddAccountActivity.class); - intent.putExtra(EDIT_ACCOUNT, account); - startActivity(intent); - } - - return true; - }); - - deleteAccountPref.setOnPreferenceClickListener(preference -> { - deleteAccount(); - return true; - }); - - opmlPref.setOnPreferenceClickListener(preference -> { - new MaterialDialog.Builder(getActivity()) - .items(R.array.opml_import_export) - .itemsCallback(((dialog, itemView, position, text) -> openOPMLMode(position))) - .show(); - return true; - }); - - notificationPref.setOnPreferenceClickListener(preference -> { - Intent intent = new Intent(getContext(), NotificationPermissionActivity.class); - intent.putExtra(ACCOUNT_ID, account.getId()); - - startActivity(intent); - return true; - }); - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - viewModel = ViewModelCompat.getViewModel(this, AccountViewModel.class); - viewModel.setAccount(account); - } - - private void deleteAccount() { - new MaterialDialog.Builder(getContext()) - .title(R.string.delete_account_question) - .positiveText(R.string.validate) - .negativeText(R.string.cancel) - .onPositive(((dialog, which) -> { - SharedPreferencesManager.remove(account.getLoginKey()); - SharedPreferencesManager.remove(account.getPasswordKey()); - - viewModel.delete(account) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new DisposableCompletableObserver() { - @Override - public void onComplete() { - getActivity().finish(); - } - - @Override - public void onError(Throwable e) { - Utils.showSnackbar(getView(), e.getMessage()); - } - }); - })) - .show(); - } - - private void openOPMLMode(int position) { - if (position == 0) { - OPMLHelper.openFileIntent(this); - } else { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { - if (PermissionManager.isPermissionGranted(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - exportAsOPMLFile(); - } else { - requestExternalStoragePermission(); - } - } else { - exportAsOPMLFile(); - } - } - } - - // region opml import - - @Override - public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - if (requestCode == OPEN_OPML_FILE_REQUEST && resultCode == RESULT_OK && data != null) { - Uri uri = data.getData(); - - MaterialDialog dialog = new MaterialDialog.Builder(getActivity()) - .title(R.string.opml_processing) - .content(R.string.operation_takes_time) - .progress(true, 100) - .cancelable(false) - .show(); - - try { - parseOPMLFile(uri, dialog); - } catch (FileNotFoundException e) { - Log.d(TAG, e.getMessage()); - displayErrorMessage(); - } - } - - super.onActivityResult(requestCode, resultCode, data); - } - - private void parseOPMLFile(Uri uri, MaterialDialog dialog) throws FileNotFoundException { - viewModel.parseOPMLFile(uri, getContext()) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new DisposableCompletableObserver() { - @Override - public void onComplete() { - dialog.dismiss(); - } - - @Override - public void onError(Throwable e) { - dialog.dismiss(); - - displayErrorMessage(); - } - }); - } - - private void displayErrorMessage() { - new MaterialDialog.Builder(getActivity()) - .title(R.string.processing_file_failed) - .neutralText(R.string.cancel) - .iconRes(R.drawable.ic_error) - .show(); - } - - //endregion - - //region opml export - - private void exportAsOPMLFile() { - String fileName = "subscriptions.opml"; - - try { - String path = FileUtils.writeDownloadFile(getContext(), fileName, "text/x-opml", outputStream -> { - Map> folderListMap = viewModel.getFoldersWithFeeds() - .subscribeOn(Schedulers.io()) - .blockingGet(); - - - /*OPMLParser.write(folderListMap, outputStream) - .blockingAwait();*/ - - return Unit.INSTANCE; - }); - - displayNotification(fileName, path); - } catch (Exception e) { - displayErrorMessage(); - } - - } - - private void displayNotification(String name, String absolutePath) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(Uri.parse(absolutePath), "text/plain"); - - int intentFlag; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - intentFlag = PendingIntent.FLAG_IMMUTABLE; - } else { - intentFlag = PendingIntent.FLAG_IMMUTABLE; - } - - Notification notification = new NotificationCompat.Builder(getContext(), ReadropsApp.OPML_EXPORT_CHANNEL_ID) - .setContentTitle(getString(R.string.opml_export)) - .setContentText(name) - .setSmallIcon(R.drawable.ic_notif) - .setContentIntent(PendingIntent.getActivity(getContext(), 0, intent, intentFlag)) - .setAutoCancel(true) - .build(); - - NotificationManagerCompat manager = NotificationManagerCompat.from(getContext()); - manager.notify(2, notification); - } - - private void requestExternalStoragePermission() { - PermissionManager.requestPermissions(this, WRITE_EXTERNAL_STORAGE_REQUEST, - Manifest.permission.WRITE_EXTERNAL_STORAGE); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - if (requestCode == WRITE_EXTERNAL_STORAGE_REQUEST) { - if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { - - if (shouldShowRequestPermissionRationale(permissions[0])) { - Utils.showSnackBarWithAction(getView(), getString(R.string.external_storage_opml_export), - getString(R.string.try_again), v -> requestExternalStoragePermission()); - } else { - Utils.showSnackBarWithAction(getView(), getString(R.string.external_storage_opml_export), - getString(R.string.permissions), v -> { - Intent intent = new Intent(); - intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); - intent.setData(Uri.fromParts("package", getContext().getPackageName(), null)); - getContext().startActivity(intent); - }); - } - } else { - exportAsOPMLFile(); - } - } - } - - //endregion -} diff --git a/app/src/main/java/com/readrops/app/settings/SettingsActivity.java b/app/src/main/java/com/readrops/app/settings/SettingsActivity.java deleted file mode 100644 index 06b0a639..00000000 --- a/app/src/main/java/com/readrops/app/settings/SettingsActivity.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.readrops.app.settings; - -import android.os.Bundle; -import android.view.MenuItem; - -import androidx.appcompat.app.AppCompatActivity; -import androidx.fragment.app.Fragment; - -import com.readrops.app.R; -import com.readrops.db.entities.account.Account; - -import static com.readrops.app.utils.ReadropsKeys.ACCOUNT; -import static com.readrops.app.utils.ReadropsKeys.SETTINGS; - -public class SettingsActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_settings); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - Account account = getIntent().getParcelableExtra(ACCOUNT); - - SettingsKey settingsKey = SettingsKey.values()[getIntent().getIntExtra(SETTINGS, -1)]; - Fragment fragment = null; - - switch (settingsKey) { - case ACCOUNT_SETTINGS: - fragment = AccountSettingsFragment.newInstance(account); - setTitle(account.getAccountName()); - break; - case SETTINGS: - fragment = new SettingsFragment(); - setTitle(R.string.settings); - break; - } - - getSupportFragmentManager() - .beginTransaction() - .replace(R.id.settings_activity_fragment, fragment) - .commit(); - } - - public enum SettingsKey { - ACCOUNT_SETTINGS, - SETTINGS - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - finish(); - return true; - } - - return super.onOptionsItemSelected(item); - } -} diff --git a/app/src/main/java/com/readrops/app/settings/SettingsFragment.java b/app/src/main/java/com/readrops/app/settings/SettingsFragment.java deleted file mode 100644 index 5c7dbca5..00000000 --- a/app/src/main/java/com/readrops/app/settings/SettingsFragment.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.readrops.app.settings; - -import static com.readrops.app.utils.ReadropsKeys.FEEDS; - -import android.content.Intent; -import android.os.Bundle; -import android.util.Pair; - -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatDelegate; -import androidx.preference.Preference; -import androidx.preference.PreferenceFragmentCompat; -import androidx.work.Constraints; -import androidx.work.ExistingPeriodicWorkPolicy; -import androidx.work.NetworkType; -import androidx.work.PeriodicWorkRequest; -import androidx.work.WorkManager; - -import com.readrops.app.R; -import com.readrops.app.notifications.sync.SyncWorker; -import com.readrops.app.utils.feedscolors.FeedsColorsIntentService; -import com.readrops.db.Database; - -import org.koin.java.KoinJavaComponent; - -import java.util.ArrayList; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -public class SettingsFragment extends PreferenceFragmentCompat { - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - addPreferencesFromResource(R.xml.preferences); - - Preference feedsColorsPreference = findPreference("reload_feeds_colors"); - Preference themePreference = findPreference("dark_theme"); - Preference synchroPreference = findPreference("auto_synchro"); - - - AtomicBoolean serviceStarted = new AtomicBoolean(false); - feedsColorsPreference.setOnPreferenceClickListener(preference -> { - Database database = KoinJavaComponent.get(Database.class); - - database.feedDao().getAllFeeds().observe(getActivity(), feeds -> { - if (!serviceStarted.get()) { - Intent intent = new Intent(getContext(), FeedsColorsIntentService.class); - intent.putParcelableArrayListExtra(FEEDS, new ArrayList<>(feeds)); - - getContext().startService(intent); - serviceStarted.set(true); - } - }); - - return true; - }); - - themePreference.setOnPreferenceChangeListener((preference, newValue) -> { - if (newValue.equals(getString(R.string.theme_value_light))) { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); - } else if (newValue.equals(getString(R.string.theme_value_dark))) { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); - } else { - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); - } - - return true; - }); - - synchroPreference.setOnPreferenceChangeListener(((preference, newValue) -> { - WorkManager workManager = WorkManager.getInstance(getContext()); - Pair interval = getWorkerInterval((String) newValue); - - if (interval != null) { - Constraints constraints = new Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build(); - - PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(SyncWorker.class, interval.first, interval.second) - .addTag(SyncWorker.Companion.getTAG()) - .setConstraints(constraints) - .setInitialDelay(interval.first, interval.second) - .build(); - - workManager.enqueueUniquePeriodicWork(SyncWorker.Companion.getTAG(), ExistingPeriodicWorkPolicy.REPLACE, request); - } else { - workManager.cancelAllWorkByTag(SyncWorker.Companion.getTAG()); - } - - return true; - })); - } - - @Nullable - private Pair getWorkerInterval(String newValue) { - int interval; - TimeUnit timeUnit; - - switch (newValue) { - case "0.30": - interval = 30; - timeUnit = TimeUnit.MINUTES; - break; - case "1": - interval = 1; - timeUnit = TimeUnit.HOURS; - break; - case "2": - interval = 2; - timeUnit = TimeUnit.HOURS; - break; - case "3": - interval = 3; - timeUnit = TimeUnit.HOURS; - break; - case "6": - interval = 6; - timeUnit = TimeUnit.HOURS; - break; - case "12": - interval = 12; - timeUnit = TimeUnit.HOURS; - break; - case "24": - interval = 1; - timeUnit = TimeUnit.DAYS; - break; - default: - return null; - } - - return new Pair<>(interval, timeUnit); - } - -} diff --git a/app/src/main/java/com/readrops/app/utils/FileUtils.kt b/app/src/main/java/com/readrops/app/utils/FileUtils.kt deleted file mode 100644 index e0aba2ff..00000000 --- a/app/src/main/java/com/readrops/app/utils/FileUtils.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.readrops.app.utils - -import android.content.ContentValues -import android.content.Context -import android.os.Build -import android.os.Environment -import android.provider.MediaStore -import androidx.annotation.RequiresApi -import java.io.File -import java.io.FileOutputStream -import java.io.OutputStream - -object FileUtils { - - @JvmStatic - fun writeDownloadFile(context: Context, fileName: String, mimeType: String, listener: (OutputStream) -> Unit): String { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) - writeFileApi29(context, fileName, mimeType, listener) - else - writeFileApi28(fileName, listener) - } - - @RequiresApi(Build.VERSION_CODES.Q) - private fun writeFileApi29(context: Context, fileName: String, mimeType: String, listener: (OutputStream) -> Unit): String { - val resolver = context.contentResolver - val downloadsUri = MediaStore.Downloads - .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) - - val fileDetails = ContentValues().apply { - put(MediaStore.Downloads.DISPLAY_NAME, fileName) - put(MediaStore.Downloads.IS_PENDING, 1) - put(MediaStore.Downloads.MIME_TYPE, mimeType) - } - - val contentUri = resolver.insert(downloadsUri, fileDetails) - - resolver.openOutputStream(contentUri!!)!!.use { stream -> - try { - listener(stream) - } catch (e: Exception) { - throw e - } finally { - stream.flush() - stream.close() - } - - fileDetails.put(MediaStore.Downloads.IS_PENDING, 0) - resolver.update(contentUri, fileDetails, null, null) - } - - fileDetails.put(MediaStore.Downloads.IS_PENDING, 0) - resolver.update(contentUri, fileDetails, null, null) - - return contentUri.path!! - } - - private fun writeFileApi28(fileName: String, listener: (OutputStream) -> Unit): String { - val filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath - val file = File(filePath, fileName) - - val outputStream = FileOutputStream(file) - listener(outputStream) - - outputStream.flush() - outputStream.close() - - return file.absolutePath - } -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/utils/HtmlParser.java b/app/src/main/java/com/readrops/app/utils/HtmlParser.java deleted file mode 100644 index b1e328eb..00000000 --- a/app/src/main/java/com/readrops/app/utils/HtmlParser.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.readrops.app.utils; - -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.readrops.api.localfeed.LocalRSSHelper; -import com.readrops.api.utils.ApiUtils; -import com.readrops.api.utils.AuthInterceptor; -import com.readrops.app.addfeed.ParsingResult; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.select.Elements; -import org.koin.java.KoinJavaComponent; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - -public final class HtmlParser { - - private static final String TAG = HtmlParser.class.getSimpleName(); - - /** - * Parse the html page to get all rss urls - * - * @param url url to request - * @return a list of rss urls with their title - */ - public static List getFeedLink(String url) { - List results = new ArrayList<>(); - - String head = getHTMLHeadFromUrl(url); - if (head != null) { - Document document = Jsoup.parse(head, url); - - Elements elements = document.select("link"); - - for (Element element : elements) { - String type = element.attributes().get("type"); - - if (LocalRSSHelper.isRSSType(type)) { - String feedUrl = element.absUrl("href"); - String label = element.attributes().get("title"); - - results.add(new ParsingResult(feedUrl, label)); - } - } - - return results; - } else { - return Collections.emptyList(); - } - } - - @Nullable - public static String getFaviconLink(@NonNull String url) { - String favUrl = null; - - String head = getHTMLHeadFromUrl(url); - if (head == null) - return null; - - Document document = Jsoup.parse(head, url); - Elements elements = document.select("link"); - - for (Element element : elements) { - if (element.attributes().get("rel").toLowerCase().contains("icon")) { - favUrl = element.absUrl("href"); - break; - } - } - - return favUrl; - } - - @Nullable - private static String getHTMLHeadFromUrl(@NonNull String url) { - long start = System.currentTimeMillis(); - - try { - Response response = KoinJavaComponent.get(OkHttpClient.class) - .newCall(new Request.Builder().url(url).build()).execute(); - KoinJavaComponent.get(AuthInterceptor.class).setCredentials(null); - - if (response.header("Content-Type").contains(ApiUtils.HTML_CONTENT_TYPE)) { - String body = response.body().string(); - String head = body.substring(body.indexOf("")); - - long end = System.currentTimeMillis(); - Log.d(TAG, "parsing time : " + (end - start)); - - return head; - } else { - return null; - } - } catch (Exception e) { - Log.d(TAG, e.getMessage()); - return null; - } - - } -} diff --git a/app/src/main/java/com/readrops/app/utils/OPMLHelper.kt b/app/src/main/java/com/readrops/app/utils/OPMLHelper.kt deleted file mode 100644 index 8bef58b9..00000000 --- a/app/src/main/java/com/readrops/app/utils/OPMLHelper.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.readrops.app.utils - -import android.app.Activity -import android.content.Intent -import androidx.fragment.app.Fragment - -object OPMLHelper { - - const val OPEN_OPML_FILE_REQUEST = 1 - - @JvmStatic - fun openFileIntent(activity: Activity) = - activity.startActivityForResult(createIntent(), OPEN_OPML_FILE_REQUEST) - - @JvmStatic - fun openFileIntent(fragment: Fragment) = - fragment.startActivityForResult(createIntent(), OPEN_OPML_FILE_REQUEST) - - - private fun createIntent(): Intent { - return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "*/*" - putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/*", "text/*")) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/utils/PermissionManager.kt b/app/src/main/java/com/readrops/app/utils/PermissionManager.kt deleted file mode 100644 index 6d72c145..00000000 --- a/app/src/main/java/com/readrops/app/utils/PermissionManager.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.readrops.app.utils - -import android.app.Activity -import android.content.Context -import android.content.pm.PackageManager -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat -import androidx.fragment.app.Fragment - -object PermissionManager { - - @JvmStatic - fun isPermissionGranted(context: Context, permission: String): Boolean = - ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED - - @JvmStatic - fun requestPermissions(activity: Activity, requestCode: Int, vararg permissions: String) = - ActivityCompat.requestPermissions(activity, permissions, requestCode) - - @JvmStatic - fun requestPermissions(fragment: Fragment, requestCode: Int, vararg permissions: String) = - fragment.requestPermissions(permissions, requestCode) - -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/utils/ReadropsGlideModule.kt b/app/src/main/java/com/readrops/app/utils/ReadropsGlideModule.kt deleted file mode 100644 index aebc4491..00000000 --- a/app/src/main/java/com/readrops/app/utils/ReadropsGlideModule.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.readrops.app.utils - -import android.content.Context -import com.bumptech.glide.Glide -import com.bumptech.glide.Registry -import com.bumptech.glide.annotation.GlideModule -import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader -import com.bumptech.glide.load.model.GlideUrl -import com.bumptech.glide.module.AppGlideModule -import okhttp3.OkHttpClient -import org.koin.core.component.KoinComponent -import org.koin.core.component.get -import java.io.InputStream - -@GlideModule -class ReadropsGlideModule : AppGlideModule(), KoinComponent { - - override fun registerComponents(context: Context, glide: Glide, registry: Registry) { - val factory = OkHttpUrlLoader.Factory(get()) - - glide.registry.replace(GlideUrl::class.java, InputStream::class.java, factory) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/utils/ReadropsKeys.kt b/app/src/main/java/com/readrops/app/utils/ReadropsKeys.kt deleted file mode 100644 index 01833caf..00000000 --- a/app/src/main/java/com/readrops/app/utils/ReadropsKeys.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.readrops.app.utils - -object ReadropsKeys { - - const val ACCOUNT = "ACCOUNT_KEY" - const val ACCOUNT_ID = "ACCOUNT_ID" - const val ACCOUNT_TYPE = "ACCOUNT_TYPE_KEY" - const val EDIT_ACCOUNT = "EDIT_ACCOUNT" - - const val FROM_MAIN_ACTIVITY = "FROM_MAIN_ACTIVITY_KEY" - - const val ITEM_ID = "ITEM_ID_KEY" - const val IMAGE_URL = "IMAGE_URL_KEY" - - const val SYNCING = "SYNCING_KEY" - - const val SETTINGS = "SETTINGS_KEY" - - const val WEB_URL = "WEB_URL_KEY" - const val ACTION_BAR_COLOR = "ACTION_BAR_COLOR_KEY" - - const val FEEDS = "FEEDS" - - const val STARRED_ITEM = "STARRED_ITEM" -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/utils/SharedPreferencesManager.java b/app/src/main/java/com/readrops/app/utils/SharedPreferencesManager.java deleted file mode 100644 index 57396976..00000000 --- a/app/src/main/java/com/readrops/app/utils/SharedPreferencesManager.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.readrops.app.utils; - -import android.content.SharedPreferences; - -import androidx.annotation.NonNull; - -import org.koin.java.KoinJavaComponent; - -public final class SharedPreferencesManager { - - public static void writeValue(String key, Object value) { - SharedPreferences sharedPref = KoinJavaComponent.get(SharedPreferences.class); - SharedPreferences.Editor editor = sharedPref.edit(); - - if (value instanceof Boolean) - editor.putBoolean(key, (Boolean) value); - else if (value instanceof String) - editor.putString(key, (String) value); - - editor.apply(); - } - - public static void writeValue(SharedPrefKey sharedPrefKey, Object value) { - writeValue(sharedPrefKey.key, value); - } - - public static int readInt(SharedPrefKey sharedPrefKey) { - SharedPreferences sharedPreferences = KoinJavaComponent.get(SharedPreferences.class); - return sharedPreferences.getInt(sharedPrefKey.key, sharedPrefKey.getIntDefaultValue()); - } - - public static boolean readBoolean(SharedPrefKey sharedPrefKey) { - SharedPreferences sharedPreferences = KoinJavaComponent.get(SharedPreferences.class); - return sharedPreferences.getBoolean(sharedPrefKey.key, sharedPrefKey.getBooleanDefaultValue()); - } - - public static String readString(String key) { - SharedPreferences sharedPreferences = KoinJavaComponent.get(SharedPreferences.class); - return sharedPreferences.getString(key, null); - } - - public static String readString(SharedPrefKey sharedPrefKey) { - SharedPreferences sharedPreferences = KoinJavaComponent.get(SharedPreferences.class); - return sharedPreferences.getString(sharedPrefKey.key, sharedPrefKey.getStringDefaultValue()); - } - - public static void remove(String key) { - SharedPreferences sharedPreferences = KoinJavaComponent.get(SharedPreferences.class); - SharedPreferences.Editor editor = sharedPreferences.edit(); - - editor.remove(key); - editor.apply(); - } - - public enum SharedPrefKey { - SHOW_READ_ARTICLES("show_read_articles", false), - ITEMS_TO_PARSE_MAX_NB("items_to_parse_max_nb", "20"), - OPEN_ITEMS_IN("open_items_in", "0"), - DARK_THEME("dark_theme", "false"), - AUTO_SYNCHRO("auto_synchro", "0"), - HIDE_FEEDS("hide_feeds", false), - MARK_ITEMS_READ_ON_SCROLL("mark_items_read", false); - - @NonNull - private String key; - @NonNull - private Object defaultValue; - - public boolean getBooleanDefaultValue() { - return Boolean.valueOf(defaultValue.toString()); - } - - public String getStringDefaultValue() { - return (String) defaultValue; - } - - public int getIntDefaultValue() { - return Integer.parseInt(defaultValue.toString()); - } - - SharedPrefKey(@NonNull String key, @NonNull Object defaultValue) { - this.key = key; - this.defaultValue = defaultValue; - } - } -} diff --git a/app/src/main/java/com/readrops/app/utils/Utils.java b/app/src/main/java/com/readrops/app/utils/Utils.java deleted file mode 100644 index 1e3047ac..00000000 --- a/app/src/main/java/com/readrops/app/utils/Utils.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.readrops.app.utils; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.ShapeDrawable; -import android.graphics.drawable.shapes.OvalShape; -import android.view.View; - -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; - -import com.google.android.material.snackbar.Snackbar; - -import org.koin.java.KoinJavaComponent; - -import java.io.InputStream; -import java.util.Locale; - -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - -public final class Utils { - - public static final String HTTP_PREFIX = "http://"; - - public static final String HTTPS_PREFIX = "https://"; - - private static final int AVERAGE_WORDS_PER_MINUTE = 250; - - public static Bitmap getImageFromUrl(String url) { - try { - Request request = new Request.Builder().url(url).build(); - - Response response = KoinJavaComponent.get(OkHttpClient.class).newCall(request).execute(); - - if (response.isSuccessful()) { - InputStream inputStream = response.body().byteStream(); - return BitmapFactory.decodeStream(inputStream); - } else - return null; - } catch (Exception e) { - return null; // no way to get the favicon - } - } - - public static double readTimeFromString(String value) { - int nbWords = value.split("\\s+").length; - - return (double) nbWords / AVERAGE_WORDS_PER_MINUTE; - } - - public static String getCssColor(@ColorInt int color) { - return String.format(Locale.US, "rgba(%d,%d,%d,%.2f)", - Color.red(color), - Color.green(color), - Color.blue(color), - Color.alpha(color) / 255.0); - } - - public static boolean isTypeImage(@NonNull String type) { - return type.equals("image") || type.equals("image/jpeg") || type.equals("image/jpg") - || type.equals("image/png"); - } - - public static void setDrawableColor(Drawable drawable, @ColorInt int color) { - drawable.mutate().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)); - } - - public static Drawable drawableWithColor(@ColorInt int color) { - ShapeDrawable drawable = new ShapeDrawable(new OvalShape()); - drawable.setIntrinsicWidth(50); - drawable.setIntrinsicHeight(50); - - drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN)); - - return drawable; - } - - - public static void showSnackBarWithAction(View root, String message, String action, View.OnClickListener listener) { - Snackbar snackbar = Snackbar.make(root, message, Snackbar.LENGTH_LONG); - snackbar.setAction(action, listener); - - snackbar.show(); - } - - public static void showSnackbar(View root, String message) { - Snackbar snackbar = Snackbar.make(root, message, Snackbar.LENGTH_LONG); - snackbar.show(); - } - - public static Bitmap getBitmapFromDrawable(Drawable drawable) { - Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), - drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - - return bitmap; - - } - - public static boolean isColorTooBright(@ColorInt int color) { - return getColorLuma(color) > 210; - } - - public static boolean isColorTooDark(@ColorInt int color) { - return getColorLuma(color) < 40; - } - - private static double getColorLuma(@ColorInt int color) { - int r = (color >> 16) & 0xff; - int g = (color >> 8) & 0xff; - int b = (color >> 0) & 0xff; - - return 0.2126 * r + 0.7152 * g + 0.0722 * b; - } -} diff --git a/app/src/main/java/com/readrops/app/utils/customviews/CustomExpandableBadgeDrawerItem.java b/app/src/main/java/com/readrops/app/utils/customviews/CustomExpandableBadgeDrawerItem.java deleted file mode 100644 index 31277365..00000000 --- a/app/src/main/java/com/readrops/app/utils/customviews/CustomExpandableBadgeDrawerItem.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.readrops.app.utils.customviews; - -import android.content.Context; -import android.graphics.Color; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.LayoutRes; -import androidx.annotation.StringRes; - -import com.mikepenz.fastadapter.IClickable; -import com.mikepenz.fastadapter.IItem; -import com.mikepenz.fastadapter.listeners.OnClickListener; -import com.mikepenz.iconics.IconicsDrawable; -import com.mikepenz.materialdrawer.Drawer; -import com.mikepenz.materialdrawer.holder.BadgeStyle; -import com.mikepenz.materialdrawer.holder.ColorHolder; -import com.mikepenz.materialdrawer.holder.StringHolder; -import com.mikepenz.materialdrawer.icons.MaterialDrawerFont; -import com.mikepenz.materialdrawer.model.BaseDescribeableDrawerItem; -import com.mikepenz.materialdrawer.model.BaseViewHolder; -import com.mikepenz.materialdrawer.model.interfaces.ColorfulBadgeable; -import com.readrops.app.R; - -import java.util.List; - -/** - * This a simple modification of original ExpandableBadgeDrawerItem from MaterialDrawer lib to get two click events from an expandable drawer item - */ -public class CustomExpandableBadgeDrawerItem extends BaseDescribeableDrawerItem - implements ColorfulBadgeable, IClickable { - - protected ColorHolder arrowColor; - - protected int arrowRotationAngleStart = 0; - - protected int arrowRotationAngleEnd = 180; - - protected StringHolder mBadge; - protected BadgeStyle mBadgeStyle = new BadgeStyle(); - - @Override - public int getType() { - return R.id.material_drawer_item_expandable_badge; - } - - @Override - @LayoutRes - public int getLayoutRes() { - return R.layout.custom_expandable_drawer_item; - } - - @Override - public void bindView(CustomExpandableBadgeDrawerItem.ViewHolder viewHolder, List payloads) { - super.bindView(viewHolder, payloads); - - Context ctx = viewHolder.itemView.getContext(); - //bind the basic view parts - bindViewHelper(viewHolder); - - //set the text for the badge or hide - boolean badgeVisible = StringHolder.applyToOrHide(mBadge, viewHolder.badge); - //style the badge if it is visible - if (true) { - mBadgeStyle.style(viewHolder.badge, getTextColorStateList(getColor(ctx), getSelectedTextColor(ctx))); - viewHolder.badgeContainer.setVisibility(View.VISIBLE); - } else { - viewHolder.badgeContainer.setVisibility(View.GONE); - } - - //define the typeface for our textViews - if (getTypeface() != null) { - viewHolder.badge.setTypeface(getTypeface()); - } - - //make sure all animations are stopped - if (viewHolder.arrow.getDrawable() instanceof IconicsDrawable) { - ((IconicsDrawable) viewHolder.arrow.getDrawable()).color(this.arrowColor != null ? this.arrowColor.color(ctx) : getIconColor(ctx)); - } - viewHolder.arrow.clearAnimation(); - if (!isExpanded()) { - viewHolder.arrow.setRotation(this.arrowRotationAngleStart); - } else { - viewHolder.arrow.setRotation(this.arrowRotationAngleEnd); - } - - //call the onPostBindView method to trigger post bind view actions (like the listener to modify the item if required) - onPostBindView(this, viewHolder.itemView); - } - - @Override - public CustomExpandableBadgeDrawerItem withOnDrawerItemClickListener(Drawer.OnDrawerItemClickListener onDrawerItemClickListener) { - mOnDrawerItemClickListener = null; - return this; - } - - @Override - public Drawer.OnDrawerItemClickListener getOnDrawerItemClickListener() { - return null; - } - - @Override - public CustomExpandableBadgeDrawerItem withBadge(StringHolder badge) { - this.mBadge = badge; - return this; - } - - @Override - public CustomExpandableBadgeDrawerItem withBadge(String badge) { - this.mBadge = new StringHolder(badge); - return this; - } - - @Override - public CustomExpandableBadgeDrawerItem withBadge(@StringRes int badgeRes) { - this.mBadge = new StringHolder(badgeRes); - return this; - } - - @Override - public CustomExpandableBadgeDrawerItem withBadgeStyle(BadgeStyle badgeStyle) { - this.mBadgeStyle = badgeStyle; - return this; - } - - public StringHolder getBadge() { - return mBadge; - } - - public BadgeStyle getBadgeStyle() { - return mBadgeStyle; - } - - @Override - public ViewHolder getViewHolder(View v) { - return new ViewHolder(v); - } - - @Override - public IItem withOnItemPreClickListener(OnClickListener onItemPreClickListener) { - return null; - } - - @Override - public OnClickListener getOnPreItemClickListener() { - return null; - } - - @Override - public IItem withOnItemClickListener(OnClickListener onItemClickListener) { - return null; - } - - @Override - public OnClickListener getOnItemClickListener() { - return null; - } - - public static class ViewHolder extends BaseViewHolder { - public ImageView arrow; - public View badgeContainer; - public TextView badge; - - public ViewHolder(View view) { - super(view); - badgeContainer = view.findViewById(R.id.material_drawer_badge_container); - badge = view.findViewById(R.id.material_drawer_badge); - arrow = view.findViewById(R.id.material_drawer_arrow); - arrow.setImageDrawable(new IconicsDrawable(view.getContext(), MaterialDrawerFont.Icon.mdf_expand_more).sizeDp(16).paddingDp(2).color(Color.BLACK)); - } - } -} diff --git a/app/src/main/java/com/readrops/app/utils/customviews/EmptyListView.kt b/app/src/main/java/com/readrops/app/utils/customviews/EmptyListView.kt deleted file mode 100644 index c353c825..00000000 --- a/app/src/main/java/com/readrops/app/utils/customviews/EmptyListView.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.readrops.app.utils.customviews - -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import android.widget.LinearLayout -import com.readrops.app.R -import com.readrops.app.databinding.EmptyListViewBinding - -/** - * A simple custom view to display a empty list message - */ -class EmptyListView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { - - val binding: EmptyListViewBinding = EmptyListViewBinding.inflate(LayoutInflater.from(context), this, true) - - init { - val attributes = context.obtainStyledAttributes(attrs, R.styleable.EmptyListView) - binding.emptyListImage.setImageDrawable(attributes.getDrawable(R.styleable.EmptyListView_image)) - binding.emptyListText.text = attributes.getString(R.styleable.EmptyListView_text) - - attributes.recycle() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/utils/customviews/ReadropsItemTouchCallback.kt b/app/src/main/java/com/readrops/app/utils/customviews/ReadropsItemTouchCallback.kt deleted file mode 100644 index 8c61fb14..00000000 --- a/app/src/main/java/com/readrops/app/utils/customviews/ReadropsItemTouchCallback.kt +++ /dev/null @@ -1,141 +0,0 @@ -package com.readrops.app.utils.customviews - -import android.content.Context -import android.graphics.Canvas -import android.graphics.drawable.ColorDrawable -import android.graphics.drawable.Drawable -import android.view.View -import androidx.annotation.ColorInt -import androidx.annotation.DrawableRes -import androidx.annotation.Nullable -import androidx.core.content.ContextCompat -import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.RecyclerView -import kotlin.math.abs - -class ReadropsItemTouchCallback(private val context: Context, private val config: Config) : - ItemTouchHelper.SimpleCallback(config.dragDirs, config.swipeDirs) { - - private val iconHorizontalMargin = 40 - - override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { - config.moveCallback?.onMove() - return true - } - - override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { - config.swipeCallback?.onSwipe(viewHolder, direction) - } - - override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) { - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) - - val background: ColorDrawable - var icon: Drawable? = null - val itemView: View = viewHolder.itemView - var draw = true // variable used to draw under some conditions - - // do not draw anymore if the view has reached the screen's left/right side - if (abs(dX).toInt() == itemView.right) { - draw = false - } else if (abs(dX).toInt() == 0) { - draw = true - } - - // left swipe - if (dX > 0 && config.leftDraw != null && draw) { - background = ColorDrawable(config.leftDraw.bgColor) - background.setBounds(itemView.left, itemView.top, dX.toInt(), itemView.bottom) - - icon = config.leftDraw.drawable - ?: ContextCompat.getDrawable(context, config.leftDraw.iconRes)!! - val iconMargin = (itemView.height - icon.intrinsicHeight) / 2 - icon.setBounds(itemView.left + iconHorizontalMargin, itemView.top + iconMargin, - itemView.left + iconHorizontalMargin + icon.intrinsicWidth, itemView.bottom - iconMargin) - // right swipe - } else if (dX < 0 && config.rightDraw != null && draw) { - background = ColorDrawable(config.rightDraw.bgColor) - background.setBounds(itemView.right + dX.toInt(), itemView.top, itemView.right, itemView.bottom) - - icon = config.rightDraw.drawable - ?: ContextCompat.getDrawable(context, config.rightDraw.iconRes)!! - val iconMargin = (itemView.height - icon.intrinsicHeight) / 2 - icon.setBounds(itemView.right - iconHorizontalMargin - icon.intrinsicWidth, itemView.top + iconMargin, - itemView.right - iconHorizontalMargin, itemView.bottom - iconMargin) - } else { - background = ColorDrawable() - } - - background.draw(c) - - if (dX > 0) - c.clipRect(itemView.left, itemView.top, dX.toInt(), itemView.bottom) - else if (dX < 0) - c.clipRect(itemView.right + dX.toInt(), itemView.top, itemView.right, itemView.bottom) - - icon?.draw(c) - } - - override fun isItemViewSwipeEnabled(): Boolean { - return config.swipeCallback != null - } - - override fun isLongPressDragEnabled(): Boolean { - return config.moveCallback != null - } - - interface MoveCallback { - fun onMove() - } - - interface SwipeCallback { - fun onSwipe(viewHolder: RecyclerView.ViewHolder, direction: Int) - } - - class SwipeDraw(@ColorInt val bgColor: Int, @DrawableRes val iconRes: Int = 0, val drawable: Drawable?) - - class Config(val dragDirs: Int = 0, val swipeDirs: Int = 0, val moveCallback: MoveCallback? = null, - val swipeCallback: SwipeCallback? = null, val leftDraw: SwipeDraw? = null, val rightDraw: SwipeDraw? = null) { - - private constructor(builder: Builder) : this(builder.dragDirs, builder.swipeDirs, - builder.moveCallback, builder.swipeCallback, builder.leftDraw, builder.rightDraw) - - class Builder { - var dragDirs: Int = 0 - private set - - var swipeDirs: Int = 0 - private set - - var moveCallback: MoveCallback? = null - private set - - var swipeCallback: SwipeCallback? = null - private set - - var leftDraw: SwipeDraw? = null - private set - - var rightDraw: SwipeDraw? = null - private set - - fun dragDirs(dragDirs: Int) = apply { this.dragDirs = dragDirs } - - fun swipeDirs(swipeDirs: Int) = apply { this.swipeDirs = swipeDirs } - - fun moveCallback(moveCallback: MoveCallback) = apply { this.moveCallback = moveCallback } - - fun swipeCallback(swipeCallback: SwipeCallback) = apply { this.swipeCallback = swipeCallback } - - fun leftDraw(@ColorInt bgColor: Int, @DrawableRes iconRes: Int, @Nullable icon: Drawable? = null) = apply { leftDraw = SwipeDraw(bgColor, iconRes, icon) } - - fun rightDraw(@ColorInt bgColor: Int, @DrawableRes iconRes: Int, @Nullable icon: Drawable? = null) = apply { this.rightDraw = SwipeDraw(bgColor, iconRes, icon) } - - fun build(): Config { - return Config(this) - } - } - } - - -} \ No newline at end of file diff --git a/app/src/main/java/com/readrops/app/utils/customviews/ReadropsWebView.java b/app/src/main/java/com/readrops/app/utils/customviews/ReadropsWebView.java deleted file mode 100644 index 60d35341..00000000 --- a/app/src/main/java/com/readrops/app/utils/customviews/ReadropsWebView.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.readrops.app.utils.customviews; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.util.Base64; -import android.webkit.WebSettings; -import android.webkit.WebView; - -import androidx.annotation.ColorInt; -import androidx.annotation.Nullable; - -import com.readrops.app.R; -import com.readrops.app.utils.Utils; -import com.readrops.db.pojo.ItemWithFeed; - -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; -import org.jsoup.parser.Parser; -import org.jsoup.select.Elements; - -public class ReadropsWebView extends WebView { - - private ItemWithFeed itemWithFeed; - - @ColorInt - private int textColor; - @ColorInt - private int backgroundColor; - - public ReadropsWebView(Context context, AttributeSet attrs) { - super(context, attrs); - - getColors(context, attrs); - init(); - } - - public void setItem(ItemWithFeed itemWithFeed) { - this.itemWithFeed = itemWithFeed; - - String text = getText(); - String base64Content = null; - - if (text != null) - base64Content = Base64.encodeToString(text.getBytes(), Base64.NO_PADDING); - - loadData(base64Content, "text/html; charset=utf-8", "base64"); - } - - public String getItemContent() { - String content = itemWithFeed.getItem().getContent(); - return content; - } - - private void getColors(Context context, AttributeSet attrs) { - TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ReadropsWebView); - textColor = typedArray.getColor(R.styleable.ReadropsWebView_textColor, 0); - backgroundColor = typedArray.getColor(R.styleable.ReadropsWebView_backgroundColor, 0); - - typedArray.recycle(); - } - - @SuppressLint("SetJavaScriptEnabled") - private void init() { - if (!isInEditMode()) { - WebSettings settings = getSettings(); - - settings.setJavaScriptEnabled(true); - settings.setBuiltInZoomControls(true); - settings.setDisplayZoomControls(false); - } - - setVerticalScrollBarEnabled(false); - setBackgroundColor(backgroundColor); - } - - @Nullable - private String getText() { - if (itemWithFeed.getItem().getText() != null) { - Document document; - - if (itemWithFeed.getWebsiteUrl() != null) - document = Jsoup.parse(Parser.unescapeEntities(itemWithFeed.getItem().getText(), false), itemWithFeed.getWebsiteUrl()); - else - document = Jsoup.parse(Parser.unescapeEntities(itemWithFeed.getItem().getText(), false)); - - formatDocument(document); - - int color = itemWithFeed.getColor() != 0 ? itemWithFeed.getColor() : getResources().getColor(R.color.colorPrimary); - return getContext().getString(R.string.webview_html_template, - Utils.getCssColor(itemWithFeed.getBgColor() != 0 ? itemWithFeed.getBgColor() : - color), - Utils.getCssColor(this.textColor), - Utils.getCssColor(backgroundColor), - document.body().html()); - - } else - return null; - } - - private void formatDocument(Document document) { - Elements elements = document.select("figure,figcaption"); - for (Element element : elements) { - element.unwrap(); - } - - elements.clear(); - elements = document.select("div,span"); - - for (Element element : elements) { - element.clearAttributes(); - } - } - - -} diff --git a/app/src/main/java/com/readrops/app/utils/feedscolors/FeedColors.kt b/app/src/main/java/com/readrops/app/utils/feedscolors/FeedColors.kt deleted file mode 100644 index 17a18c7f..00000000 --- a/app/src/main/java/com/readrops/app/utils/feedscolors/FeedColors.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.readrops.app.utils.feedscolors - -import androidx.palette.graphics.Palette -import com.readrops.app.utils.HtmlParser -import com.readrops.app.utils.Utils -import com.readrops.db.entities.Feed - -fun setFeedColors(feed: Feed) { - getFaviconLink(feed) - - if (feed.iconUrl != null) { - val bitmap = Utils.getImageFromUrl(feed.iconUrl) ?: return - val palette = Palette.from(bitmap).generate() - - val dominantSwatch = palette.dominantSwatch - feed.textColor = if (dominantSwatch != null && !Utils.isColorTooBright(dominantSwatch.rgb) - && !Utils.isColorTooDark(dominantSwatch.rgb)) { - dominantSwatch.rgb - } else 0 - - - val mutedSwatch = palette.mutedSwatch - feed.backgroundColor = if (mutedSwatch != null && !Utils.isColorTooBright(mutedSwatch.rgb) - && !Utils.isColorTooDark(mutedSwatch.rgb)) { - mutedSwatch.rgb - } else 0 - } -} - -fun getFaviconLink(feed: Feed) { - feed.iconUrl = if (feed.iconUrl != null) - feed.iconUrl - else - HtmlParser.getFaviconLink(feed.siteUrl!!) -} - - - diff --git a/app/src/main/java/com/readrops/app/utils/feedscolors/FeedsColorsIntentService.kt b/app/src/main/java/com/readrops/app/utils/feedscolors/FeedsColorsIntentService.kt deleted file mode 100644 index 464b0603..00000000 --- a/app/src/main/java/com/readrops/app/utils/feedscolors/FeedsColorsIntentService.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.readrops.app.utils.feedscolors - -import android.app.IntentService -import android.content.Intent -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import com.readrops.app.R -import com.readrops.app.ReadropsApp -import com.readrops.app.utils.ReadropsKeys.FEEDS -import com.readrops.db.Database -import com.readrops.db.entities.Feed -import org.koin.core.component.KoinComponent -import org.koin.core.component.get - -class FeedsColorsIntentService : IntentService("FeedsColorsIntentService"), KoinComponent { - - override fun onHandleIntent(intent: Intent?) { - val feeds: List = intent!!.getParcelableArrayListExtra(FEEDS)!! - val database = get() - - val notificationBuilder = NotificationCompat.Builder(this, ReadropsApp.FEEDS_COLORS_CHANNEL_ID) - .setContentTitle(getString(R.string.get_feeds_colors)) - .setProgress(feeds.size, 0, false) - .setSmallIcon(R.drawable.ic_notif) - .setOnlyAlertOnce(true) - - startForeground(NOTIFICATION_ID, notificationBuilder.build()) - val notificationManager = NotificationManagerCompat.from(this) - - var feedsNb = 0 - feeds.forEach { - notificationBuilder.setContentText(it.name) - notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()) - setFeedColors(it) - - database.feedDao().updateColors(it.id, it.textColor, it.backgroundColor) - notificationBuilder.setProgress(feeds.size, ++feedsNb, false) - notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()) - } - - stopForeground(true) - } - - companion object { - private const val NOTIFICATION_ID = 1 - } - -} \ No newline at end of file diff --git a/app/src/main/res/color/generic_button_color_selector.xml b/app/src/main/res/color/generic_button_color_selector.xml deleted file mode 100644 index b7b39637..00000000 --- a/app/src/main/res/color/generic_button_color_selector.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable-v23/splash_background.xml b/app/src/main/res/drawable-v23/splash_background.xml deleted file mode 100644 index 08c59cad..00000000 --- a/app/src/main/res/drawable-v23/splash_background.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/header_background.png b/app/src/main/res/drawable/header_background.png deleted file mode 100644 index 7ee316403c8ad231d768bf4d7a308d11cdefc59d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154749 zcmeEv2|Sg{`}nK6wBoi=h?X0bEF~myT9j^CT9B-XqU>3+zc;O9sYH=2LdTjVTR1Kv zvL)GwRaQZJwEVX6Bh^=G>K*Ji21(`lSE>tT-lq z_!Iyvqyhk{%ZsU@l!H;S&!Im{^u^_`0sz-$^nX;>o{O3Q02^@Z@PRYes1~J4X*xg{ z-QMB(mQE`w&R=-z`hq1OHqdp;HkxgF1)U$pC ziRxE{k&0#q_JJwc1brN#bGbD@tFxU75Jp;hB@6N@p;bwar!>~8t^1|Y!slVdJE&ei z9sAT($p z7>?Le`DDl3G7KdgZ8DL%=xvf7lM@Ji*Ogl<>>Y0ZFTyo8U27FyY=p`s|M1;VL*37! z1J#%EyD_{dL;zSRp?ezudO7kFG6)%ze<&&(9~$bu(9O;loIQKK<+4e7AdhW@Q`J7< z!D!*B6H6#jhCBZfO4a4cHVjHuuWRcmJmWSSp~T4wKepuhg-85Q0a2`(d!815xb-X* z6k6RXT_E<<|3YT+KY9fs_x%(SpI;DQ|MRwNmb34{MUReieC~hWFz|h_Wq`DfOxBXX zAeJ3~87=75;14?bR-z2!;QD66a(9-@&IN&|u4l(4EjKag-?m5ar(XmybIC=Wl3ISB3zbrSjhy%b+ngq0W zAMJCO-}PUhtwUss%!d7TA)a_L=h%31sq0LBZ{ z?_k+g*xfBVV2#?ThIRPsABJ#~K^UbNzS5-XhD&q%^?9H@NtAe?J_d^{dS2wZ=wATv zacVoA=d@~7&kd%fONXdaUmX$19D3;JFk<^gKi+pw#HYj8x4iyDf4`cDvBJ0r=|PH1 z0#`{E;mVkvepb)K)$~I5DCCbI>r>##{n3MktR#sd238n!wp`ru=%i_K>8OcF@|ZT= zua*jS-;EIUdi~LYsAA7=r0%n8ygr`>%(DD<{jKrk>!6?RxnC?W?5W`|mU9Yw|6S** zou2Q)H|?E*gYyGyI}q_VdX68=cmMWsoM3a3ftV96WM*>hiPTQy0@UcW$m({Z&&$X` z9lWjOg02h1hFvf2wT>@c$w8Mm8oy&7d3Vdo9ti+Z=WCvAC$7}fun^UlWhT%1FViyS z;b^G>^xr&*ErK+y3_C1)khI0|` zgB^wpD?5|<-RdTdBjR%+D|wly8umbI3I9<1zFIhZ>FJK5!77H}08VKXkN36f>>0V{^llX4XD&JU@6(O-O}@!!bHVgj|ooPJ4p z0ZPm0Q$t&wVzF(-l5dt#_Z!y`#17;P53$n}7N^oSadK^gs)`H`N86jaFus)5g#gGc z+V#koi1iLGDdewzY5oxlfD2}}bhg;9 zOTe-q_M9Dr5$bNqRMES|l4NpRn0aT3#Y;1MEfU~i@-*Am+7h!ct2eu(!X`TbzfG}M zxtZIU+TV6H$prAI^2<=#NT`^E6$%D4zI||2p>{z0P+g?OWtqb;$dLrnqrV8KfT6~z zOa$Qcvd1awAFK3Jp&II*^w@vM?6Prz(TgHqKOnI8PnC>3Um!l8f~**%wo>vio1juF zdO+XFod?>u{<4vEFOvrTWktzq$GW4EryF8|_9`-F1fov|$SY8xUW=ij#Hd|B4R?Hn;x> zpz<4*D1&FLG{${KaiXugx=h^Gle1(9auSg87_m-~IC|shZC} zK76s>F@!^>cQ-lt;4@w1 zxb%Loi=X0$`t`P5Bo0=nXN*}z8z8B`KK3WYCV^GZv;^)0;l@4oy0eiA&{+FO-iUvl zd1A;JiRp8x$<**JOC|&VM(?QMeg~(T%Vy<8opi_mO3Z({jVv+lbv1DpI=}vY0&2-# zt=5nqJ%>%R?rt62te5=#%$`{$op>kgzfrYJ6}5aMH9%Kp*7i9Sfq<{NB#d!Hcx`~+ z9~t{??OQM=@y4fbuylU|t1Zmf&6M zrO?4fK6W(qZ{ec>t2OYON*oIUl$hUE|0UpoKr+Ym!BCjpt_9#TI~agJL54amK}Qwc z<1|0~Od|dT6;XlKT3Y8e*fc=xWP^u|kleuIojbgf$0E0HhK&oHeS|qY0w7|>W-&=@ zIClAh>ZpK*HgXsw_!6TbzddL?;hrxjKj{?9WfQTWJ3j<_uKzoiBYZ1^P0-O|z zGDDSj031k7nIrH7#z5((d<(7-86VOH>}MG*!PVHQvZ-|4V_#+~c_>Eo+t;f&IvsXY zvZVI*=SjHEGQXY41WderFvV&C0AXs&;~LCoqLD2HEQ=(dW+Ap^mKREO62@%+sw#~& zc(wp5aU3OL^Uzc8L%Z7M^#kdqcQ8{gP4~3m->Wr@_CeJ|`op6jOPz@hCly)fsg*O-I+(64T)B{G>&x|dY^q~m~w$gTBHzu*enZ{W6b{` z@sG{5tuOh{w*6fPxeyhFPf)H}Ut3x;DR&yytN(}vaBA24Y{v;PjFn>L%ARc~1<8K2 z6~F%G41(?M=HZI?O~-*70mdexCmo@A%Ir_APPJgXg-OJtwm@F6Aet-Zg(10*GEEl1 zSIe_0-nGuGPP!RNV%7V?X6) zRlLUEWyD-LWKVH>s~}6*_-2_Hz!!i>NPX+*bp#FYMDqDXXo&lN^(sRXWK`#orZ3j` z$2+7@Q0p%e^?z(P{aW@EgR5)iv#Ee)5&zkz zySEr>l3?V}ylFJ%@G_Gy3 zRqT}G3yU)Sp{CI$#pUbbg!g?F9D{`d14yHITQL5mCk#TA1wl+FAD5UX@#fDH_NVEj z2n_C?PKo4iXF3|SEa09a{&_6PtmcjY3X1rBSAG~B@!Qk$XYwMa2InSCSKGC9^x|%{ zj|)R}S^C16t169*ib4g&4c^vD`yO1qInd>}c#oq#-U-Q!iCnfYl{ha)pZe+8xho-7 zPW;Bf9UGoAB8w${>NZO-hV?rdMaM7_{-s-g%qoDO8+$aCdLhb;m6~l$eEtcO*y1wRfsyp|ZonQy4ycOkrkfDjt^IbJVuBA|Xm67quYyB;ON=2mWNu2kZGVLR?1;YVR)f=>1;otTEMPr1n4Lk+y ztFvX)8k(q;81Wk~Bc$}R!A|!oKxW50Hf%PGd6$Q>d_V<&gIlgz$&tJYg*u1)PRZwt zK-Lo}uWMud;e_-MsJU;$#3JPMReErzkltj*RgIW;8vg|XUE90wz8b{WEozmY^>kB% zTb`mGOrDF`12i&u9&*(yc4s+S1;4J5|^JyxKtcV9JzW9PGqNORd%QgdC<-LL@5y4B*;hj9%us z_Q5}`amE)TeZhynNGrtzHpj&4@Ay{xJU^@=sBL%iP&JVPYL*4B0|5+umfo~~xKNK> z4+!BOOXtHWI!FM`|12o4!>!kHHz6IN>R-Wc^088$!5Qe@iRvg)AwW0!gXdfOTJCRO z4b=92KYPNDbs!}JX9a--5dIB8b+%Ct{D=jBZjBUPl5A=Dxvl{)vic~}RVl2E&u%O? z>-ya4gsBE$U78Ma__bHwpI2t2wpSFIbI=zxH=QS7=3U#Sb`gVE@Zq_hclGdSoBWB* zIFt|K?0Gm4exGCzeH!XG(|Po1sQ$M8)51F2`w#uGa3|lPRIZgznOPEzjjq_p96cKA z==kJs{h*t)>sE6WWXEJ>`14&7bR0j)jadG~DRY>5UmNi1CSuim9II3D>|8t zoHgjbX`s~PB2-l9lBnfRqWqNft&Ib&NoB~+Ku@~!y05YK zbT8v5j58S(ir8-G?Kog1T+M)KD{O1x&orF6e_RaX6VG!ru@b5l5H5wk=D{H{S(2UY z&^Nh$u^5jro{i)g`$v(wvjNP=46yQ03hTAxY!4LLYUC}=?_uY6|tk=NO?}(x6s~)zi+^1;dPVrl>uyKSOCAM zgp4b{^c>FqPM*PUxyeVXYi=&a_1|03eFqV9cJO{ufgqdiJOze3z0x@%7s`mFA=`fvti7TPaDOTf2A8ZJ90=g$3mgT^?|?h81TKT%r< z^Qb?7_x?Wvya*6iwN&b6DF+hmsYBdxcx!}>?2&ENc)MYbnrz}GQ$kGG zx4@|E2=D9kOl6kDG~rt#AKKXIP>K2R^q2|CWF|A;^I5w}Q%rL>6ds?Do0*F@o5b9J z2GTRpt8GrlVG4h;`9J@1qf~Iz|r>i&F|o{G3pZ35kq+;?=hNn3OC*+|A_ zecLo+X};3nek5zt%=-g@#kn%_xJJIo+(>wjI(Av8lPZgvr6;!_r)^#v0hFPVLfDnr z%8K7ZfD0SJ9J4%8r=ng=lfXEHTUD<*dMGt&)V3X30MzoLf0~xG2X$e64z>4+M4j7L z{jQA#mO^`~zv4MeJk$I#PfHgL6ZRTc!p3_ZmI2{QrrF!CnN<UHQnrd*eK4HHe^64_o8b23o5`xW;W)q^2&TJeEk5e0S7sF zJ^W!|kQ<1Nc*}A&WP4**R3xtXJ`PE(zYkrpcrAE?QUd`>P3EqjtEI~wKi61e9p!dX zEgNFA0NrSd4D!`v;L`#}bsBQ4j#~CDVa{W4W_*EWF9o>2PD(~!vvW}gbIs1TZ-OcQUT@{!`5s)j?V7DB!#2sX_NE{Yzm)8k z9*(-}JP#)r>BouPVQV~fFywZJ0dwm3=Efvy@Lo~h&!Y=C9^D2HB|?~sli@S~ga;>a zi-r|@5H?icFQa?G__utYp@w6LWR<9*fh5+##0A!-hOYPra3b0pK( zJnk@~H2CsQEhMTpj)(h!Oj0pM4^#6f{RQsIe=2jkNo;}sRe^6tW%w(>awNV_o8MIp z5-ivp=N{Dzu_c|hqSM$ieB2$#W#5Q0Z$9sKKRkJ+>B+AA88X65KpFDx;eK$LkZo6( zl<|Q@jsw!e&F!E(J`idEN52PmypoTSh zLS<(hd4MAZ*dG>!j8#o)g9D5W28FCfUZiQwCGY^rHfjy8mRm^y9q^}S*tnz9&P2U@ z%dr4}4gfJobwW{1+_(V$m#$e}ovD}d$ZTr{@G%j+GfU$0O^TkmjRWlvxB9qYo9U)Z zwg%=ct*C&UjGmP=V~EV*Wk<(QDSP=VnG=g|_->``kWTxP)8%ibumeOLQ@=r!xCY8Z zD;g9GPd6iM{l~U+VQh)qR@)7axl0~ABYnk?Hi|y3{n&vUh9I)TxPscSDGn?i)fBQr z=b*yOumKRBhB2Cl&({`J+~sCfPDn~#d6Q_}6V9C=jK|B(p-KWhU71mO@vxIbBk&*p=K~w-j z_7yoSFE=eBSPG)1y`QE|5)Wd=M#L@d- zKW*8;Bw9jKK`t}eos}6r#{4&=2>mj;6@CR_9oio7YtGVIbj{6w1`T&^yNq3x=wKgN zc3gIAY>V>Ig_XL8{49leVUbrMj z$k8ytQoGJPr_}ns#0+Sa{r{hF4e%tf9znmI4Y4CgEubR&18z8V9m?p>h}c2;qz)&8 z`I)heI?Ych@b(&Bmra|q`FI`*Y9dnx44omHhbr;uhjAhew_C%+WM|J{HNfJ(u1-e z27qh>IsoB{y_sUy7S>dK4{EUuYs^+du^W_me3%E+wJ@ZepQolpV|Of4iaBM9gp=wA z3HIX)0nJ1`oDlFb=ntu#bgYyb22|rmx=$q$IBLV=0sW+andvxXpRCitDc)GoR$@*4 z?Gmrarz`}{>JsP?o>5FxefZsB=vQn}mi*BiM`vV{C!tPeyf?5xO$pmGajFDc?$k7% zb&5MO1&||UoknD|mC_9-t4tip`>SHe5x37JHX8yKN+)^B4C8Z{Pc7xlug18*tj&;n zU{G5#K9q%k1?EgrM5YkpC_C%2S!{#EP>FRo8TXe|MVQXHY}Og*XvrsekeI9kR8v7Y z^SQ|>#JuyY`;JDFZYX>?NQkQ7^NQG~wAyT(bYcx)b=yeO6Isb9}=u7WbP$Jy@|>cTD*YL#gbf20fW$(gW{_ZjDNfgNAqDL(>IvkBjh5Y#ue#!5l|2w|7dajPeY{qAOfM|_nnAFsGaUYHwvd|3?*!(rS5Y@ME; zS8T;ms6ndJnI4oOU;;8-1k8E$qiB~&^YPOb#eRFcS6=41pMKM!f|;dj)puUe)YMdm z`}YNv?%j1*&cgFzYvr1Q+t>QiJo-j#EaG$tX8#78zTh*EvlF01Zu@g0$&&av+bqsw7oZ4kTKO9Bt(FZ$PzDT!fB8 zLRFL~8Ia{C$QGxFw!gMqpiLM0499Npv?kzxtii-6G{gxrrcTQv`SadTzW>{smi=<_ ztZ-@oA5+1jF%t_oZt7#wh`LGbAj&e#%Lz zD2(axS;(f)d4dwf0{P;d!zZ7HA+`u?+T~G+-Zt>iQA#h19S+g;Z-xg>2o(72vj4Fy ziQ#ZM6(MwS!SZ#KsVDP4p$tE5I(A%q2}$1w6Btt#N~{i|#57(7!4v`q7&=DTe+xad z^t4*;{q6p_O>5!`p4+2Ls(nm6-)kK0rv9|4qD=Q3$LTDNbA9*o3&?BQa0rDXImq!+ z{rH{Q8e|DAT#bXzZ+bl@p`^jNZ!P0xJ}v=JnN8}+m4C;RMWu5!4pUrFszU7xfIGhWHA&Ktj^^xfG2W%3 zTct={!V9)ub2TJ=1%Q^EEIn?{R4@x8l<(UwB)70TzC0`@CpyDWL&GJX^S!Me!lqaY z9UVWS#R%=(4Be^eUUqBcf$PRuwH!gVwhxc$7v&b7-urc}7h;QjmC%t0-|PM{YNG4Y z2T+&yoh9fB-C4>Hz!r5jBJ&}yR^z3CP}xEtC)eo}L2kO4!Xr1CTF9<*S^y^83v)bP z6cAO3**u;JNWl~kR`9A?)&~vTK0*qLM<8h(?-L$*-$xWG*rPJsv`GBzCa~?{!upfw z+kKll>!g>HKavz*;b6@_^=-`5g_myl%<82gN~unUP&y*;`BA?~clQ^vt-8uZv}SQT z<5SIee(NqI`dVs4(H1Dkl?^y{_`n(H67-_CUY|=y?3(cSPl1BfKKwsX&c}?9X;8~L zGk$yc6v=+oN9zds9s)2fkqkaI0?Pnv;&8%4Ql_C?gi^f*c)T+iC4^@SkHB(~0U@9v0>?2P+v|sRFRCl3YcAdCR9n=) zqWxm*z4yl(#Gd@%Fi|qoVIbaW+hlioCt^$p>4wqIJ3!{;e7j@brWO zNZra1Q?<)@TIU&K^tdd&AO5~AfdbRo)qYp6z2@A;K&9>BkMwI)(ka@4l?EP3vEuxK zL`RzVl<=|i53o!GZutZ`uG>r99ufn1Sd2X-&67vJZ4OwA8yuaZ?cOjnno(8=x%{My zPs)&YBWi-RzRY#tuBbeK5HA;p20k*=c~rEICWTb zvP6v^-IvX>I4>UJjgh6p>KXn$I$6`h_i-guW?4^J`bn;@Ugy0V8X&zuH1SpzaCQ`_ zB&SfXOrw1vQj1bS{#l1E@-kuC!P$4Wke_R?8fgUCX399540$qoLP?wzK-z{QN%{w5 zYY@^;;vpPhVqo5}KNnWVAoe;xaZJEQf*g)@Y;?(+7AM~Y4eJ$K<*SQ-5FWqE_=74G z8g}%XxrANIlT_**d>3dAFzo%l%8`X-M8=V2UqV&si#xWawe+bQuit$%XZ_-% zL|uIY012Payz8!^P0}xVWbTh!yC6GkP71^?l8P|4px}lo>tRy8p=^#vb4=QD8=(WX z5!$hYNkC3$w*AM($(@M8MwadO7@e?*PEgw8k}FmEP*?m?I1*0g;53AcKXOyQFsTh@ zyg2yhDyYDL@DowBofd&Dc;-O_DQG6J_@TPAHU@qJzVMQnnPg9a`inQ~Xk4=3>83!R z!IY9OL|ur8Q!K>#l~I>3n;3JgSOVr+^Nq#J!aw2U!$gbRG(O@=V2!B4Z*aj~-q1hI zAz1!FoQ1%bk!@KwRJYu6B&jD3tDN}o3C;fy>}eiDW>1mxt@KP&?nls3EEL}|-D=n5 z@82#JVz$}gNXJ>8+TIHJ7omr<0-I?=VDe`DUz~zb_V*8wRrtH&tYq+HHs7)}kBPeL zmpP{~3wVUWlU;k4O|ZdXJv6iry?60bXl9`~zv7Pazv)XBg~dG-v~Ju&%iPBO9vO)3xpT4qV7 zQ4uk0Slh5APDw*lIkf8H@2^vE2>sZYms}l9C=yP1kz|YWQ6j7A+M7Q6ux!N%M(zyf z=Xa5c=q>PN|1(2e73uV zg>~^+%~>I&8^&iG9yl%t!6L}Pr%FmSWlCW(jVqy_2a8H;DI6`uYQDv>nNBPOe}g2p z_M;;eqoozEvsVvZEubCav$;-x2`d+8%8pA| zc~U45ai1I73jB)m=b$LWwt#jOY|0@0C1U|(BCg4(KWswt95EV25x7N{V7(!j^B&C{Q3T6Bg@-WwP%0<__;oPfN~pW;zp>1b zmpAx9^ASGJ(~FO>ul;D^YJJ_)`jJgUhso(|{~X!bMr#x3N6}ns?Q@g~@a(kfiGbz=YL^wq26qII!6S~qCFO6J4k9IyLs(}_@C$Q~f496QG`|9SB7z@7 z4IkcPUA5ms#Af|idp3;@{pot^muG}skV0gbrh$0*i^|#}CGkT2R{&HK6A7DO1=v4t zQG-9!%&I56m)Qgky5>CGA2^6-+j>?e@G`--5%sO=`GuoG@Zc#71=@6>{RVUY;A;#B z?qxo1u;=;2%TF`1C#jf?H6-5560ZUJwsoO_0Xr2#ryB9 zJq_(|l~*TL!5+!TrK|5lU-7#8G>20!*5K>GqzeQ_H~WjhSh3Wm1z)P;Sk$fI`FG@c zlGvu7gldP)nguT%l)2nc&6hfk=%-GpzAaWs6$)K*b;fw2fTT6f2|AR1J8ZuVIGdNh zMV_<>P}e?QXnJSghcXm#U5n$s*dIj!bCdfAhkwP;#zXmj@uQF={ROuK11R6UzlpR2 z78N8A{+EHvR#?#Q4A3>b*&l{=5f581eNA(Q&yYdT96oSv{7z-qs6PNvs}AVjyUqq3 zUH-C>RVz!ro8D}_RM`q>pBRtx=-n!g`?9>Rn}PI4wY2c`~}3}>5P5K_TYDtKH$ z;T2nt?yut09NnMzT_*UW0z9<2hq9H-35;D>#Rq_lE5$@lf7van$Ev8vjZFzy+rWgx z;4|-k3YC#_Qe8Y7OjeJaD9k_cdB=nRB)su%$*X2*4xmC5=39;qRa9Ms;d>x~-4xVb zzUDWo1zImYDioUsm+i;vi~NBMHL*NN+H_~=8`}9;!G-n0OST`-IQ~9Wk)ifv2x#1o zBcGhfU|C&Z5Q7Yki=zqU#4+?l7jpUByUpSzfX5|DDCt???`-VxEfv`CiV6{B!S5Na zSp8uZ;Hg<6$!34u!t|kgw{_%VuV_?F&BuJ`T zrLS2znEa`|mMk#(@8&DCP;^VHOunm0*R;0 z`-W(&UwDlEqp*zDIQWo{UHM~G$^Eun*hc1Aihz2Rugj|PTo|}^Z8X|^kffHYzkeT( zsE3^|^mFxplteA;>w|r`P|>p!J+6WF^^hYM)&suP93O)b%dzbR;#&aw7hRo;If~ zjRf|%X{A?udJj$D42A9vY8x0oe1!@a!6qlRzB5h0|5KkR^=P;gY zU_Egb=9qdZ_)-?xi3J0JCG9_%1Troaa65|JwLsiVKgApxb?}rjCtr!O5!O5iBz`uD;4E_~1Ed>pd{IWdDdRH4w>XU}h3hlRKFSmiHT_--HsOsnNiFiEzq&&rp)#A~(+_G!iobx}2|zxWhKk`xf37E|c(JD?}E zuSo+BDV{umQQ3W6lKX@@R)@4TH_2%caMez|}XF}E}Q%>VI%r$$j3wc;rrP=QNgO@E+J@^5W#dugqDjrPku z%=m{Dub9(-bH`sK$pbYZ2pbwU);Mt{y`tDP`IVlCott_c4r36R! zo+3i9P(pH2k*M}Er?C_Ge$Q(P;>-^Yq$fR-ygM`_!P#JEBF$lnKQ>;Nn`@T-3r(0% ze(Eck*I52zTPtPx(!)<7b2+X2q8%&kA5bqz#WxwlMwz6vZe zCtS7M%K}X$Hh6nqamPDi&ZI!`6vC_}#l@i<1I8|u7~{;Ua_huh+!B^+-4tXM#@%d` zV@V8UdSZ}%OPsnh>5zYZK0e@l?5OimQ52I2+xnGa%>F;&nHrdXy9~9^FDv#iUd2)h<)3q5H2{pKvBO(Tg{93?RM zWHx3%E=s!Nlpszani;!K7fy`@pvN=DQ)_&ow@qt=eKP@t1fNr~?nq}fpyKqhIc17w zhR3mi%4|Dqmuh%5x>^ZOt&u}=MA*ZDz&D=^W6ySwg&Q-MsZPmpUI6T8F>|7eDRLmDeEp&iP?+;MV*@#ED}Wp+uiAFXT6|WZDG!USWY&?| zNlKZ-c^`{9H40IoL=WhPcpQd)3;0`3quYjtyodC0S<)+M^w_r9pKq5H*pM37S{~~3 z=_2#;mVdKq5e|HEevdZ%vqPE7+FS7O+9F@I4{-NsNfCDnE}nK?+f*TSY2ztb_9;jd z`pC(pbtxu~2?8U9;zvonW8)qB6HH*QrKM8^ET1kHJ zRZ{6SS<9$6l3p z;?XP|fq%a#DV$Of4+9DwhKqc$K3z=+(x1T{&VWM^Rj?qzr((--6c`=%I_hpzr$nBIFQDAGH-RehvQ zHNNZxcJ%)%9S7H3883przT|TxjjEzoNu&G%fGvNew`da{Qa&8E;b^TN&9<8D&djE^fVY2{3U@z7?GwJ7i5;|)+ab_Z7&6<0w{?sf!?OWdJK$=gYd$b+m zg9!HeyIwr#6;77p8Pr9Oo-I$vbr(Kt9ATc;$&89=vxc~4Mafy*A!GB%`h|OqMNGSuEoW$ zXLB%W3+iK|4d^f`giWvZOW51G4ba$#>tVyU2N-vmwT+*yf5Teydf1_|xR~KhkCyss z?6GH{=sDWVLs$GV2ftbY*?ZyZ^pZgj%r*a5SGTd?UiGf8yN9l_>@#l<;62UD@(e-! zhfo&%A7zWnT-Ej~7JtF(Oy;QY(I%~W0Wd_<{}`u)8X#?Y)IJkNlndUX{{&DMTiB_` z1^srOVT{`vOjrDwHOous3cq73aTW@%S!9z?>o!T5gfp6=7>XxE<@3A?g@S7{tj+d)6{ zqvONiG|BMUOjQ172X1dJVHwJws|Ih2hK9$U&MC&*ucnoOm`|u>^dgDhPsYyD*wh7n zM}kOPD|?+dY&qT;l=-nqqmR#OMz9>oDOP5b!Y&XwQ?vQM^$bhAMEe~6+fP=oa@F{y znVfPK73Z-n-7fu?G!WGSS(bTQAl3UNCTZx&cI}H+4KZv34;$`D$Gj~SF2Tefmd2X3 zwhAxMVB*|x<-9Oa10_00H|g(#=b_)UJ7jtDtDoYc=hQ6MJ7tdxzG~bY-LD?gPwnU+ zxoPJLTG~IBtli$Ia!s$_pk@50v2Y1Smh`h13zcl1J^c98n|T3{b>TdZa$6E7Hp&QW zi8#{!DcfH(H5LE|;5y>0w^P*syQ}=18^Ba3 z$mZb#1myAl($nMXy)c05)ZqdpWkk9Tje(m$Glyru^Jv4eJh21Rsf$15-=IxnOTxBV znyQ25SI;gk?|nAs&1|X^u)iWk1!2Qt2Yl?*1!ed%zRP3@uOPrfj-48zsrXO2_*B?= zt=$})k>!K!!Z1Sl%>rMbBbM*9DmY`lbodkI$L!Ff+46Guo|{>UM^F|%2M;uL#smcr zM3gY#+D{@6)trAp*$ESqve5Vfl%WOgwa}#3R#)Dzac`%rC|#m9wP(h6xG2jAPMk+L-;MLqp(1N))$)Ak#nuFyi)C^NP!~-hr=;{p0go!UA$pV1NqV0g zu0j$aqL9C@T9oIfHr*f@Dm673*(n^$h6-||J)h>NgW@p zBlZWE-)*x$4M@wzd{VFPISYI3b!TH?zI$)<#^k@Xy;a_Ijh$Wl7j}6FOPwzx_v}(T z2c8tYvf$f?{Q^p?vqR#`jb5KBzZn9`D#Bax7#LAd zG1uYU0`&4FBn!rH{;gu8^GJpqa}xZf6Q2fti~QG1V3|BH^MXb+2mBHtypF`mu|V)< z>#MBW872AQqvC{QgWZL3l9+=GP~+rKk_hmP*M>x96`vV2WFRwF%DRI@N>k8znq$y< zY|t`#w1*4|MNA<+Z$IKZIx2HbNEPo*FuQ;o_1;d2JbA{2918|a0ia)MiPGk_yb$#ka#odhK~^~GEvnxs=9xBD z*@XK(^@-jlo93|cn7c%%;=HR<)*f2m!@;DYi{5pZs|Ql`;5V*4!jKcLDcIBFJAU`} z|0F{-8fo)mZiWo1$SbMXSAm>3-+{T+PO?H83YcN0-()VomS!{iqt80IL{O&J#v^I87ZxG|>_Xt1+ z;By|4v9!{$wiUN+E*71+-*Me~q_Jx(ukACw2*{u85l@b~9koDzLT<0(mUK1jxBX3< z_5MWv?Oxa%p?M7Aeh1Q>au$E4QV_*P1T_hL>&F<4gS(vUv_o{U&M5ygRZ`n=6*BS2 zCepeLrXdg4aZpKdC)G&l4dqhEvY_7bjoU!!rju1z+YqJZ;xn5Sg;P-VeTfg>(nV=4 ztW)D8rR_y%3LCx&Nb` zHs`IwDP>6P6JXe+UCj7I(IM(${3cc>&vMGaMkE{HFQ^quXk$uP0<$rd7iG<-So{9N zE!t|;gETAk*&KBvc!heynX4Gfm<`f0_&EPE5L`ulnKRx!pkPO1&}RZRrwCJ#4@U`M z2s5B*htS^ktoLl`_mR3Ya>zvLFTuRps=T-KhS=+ zzoq4O(4+O22NW8UvI3njD)B)w4Prlmtp-zNf@N*%U+d-s?)dCSfiC%ULuc0eTFWz8 zfn?opn5}FTOfRc+6|M~)>>u!MdwWVoU^d{NdB_9`&92gV{yiXCcpT%0s_ngqb^d_& z(C7ce1aBj$^U*)qNAAbREgHdX_Lp}Uxe4Q-hdN+WROqug{ZEii@3czalR|ZT8-{G& zBH8aDYPFwU$hus?L6+2H73=Q?=5df%5TQ6P&J~%A%)5kiuBQxNmzY(10<86^sAcQo)^R>o_vH6#$XCiq-FMRU!Q$SL&`BZL=^YhQSjs zO@y5xCHzvI-j~yQ;91`G0RasyV51{5olss_=cM{2<`rcv3Ryg;3Uy<31eGxqYN7Dr zd*%Gl5Bh!a-G1a&P*{s4=-V}bWrENs2PstgwnPu$~!r&{`v1fA8()rHGIT#n8&0S`7BEflb4td{= zHn#kWa|)<#Z}=>0HxO)wz52*22`SW66ZZ)%XQwY}{v=0gBdXWTtymFLChcsy-j~i? z>@zlj35l%+FPAujGctq6!IizN=9I4sw;H-_Xsj>K_+$KzhZ z833%8JmX}6JW!HWcc^HTEZrGzc<>}G=$k$&uJ_7$xOq|Pju6Lb8W!8M@7X?4hE=7V3gN+|ZAeVv05*MQ&~_)U zd(MS7PH68b?}0`FqAI2cMbroQbZKz}+Ec+6cyLcUS*B-q2oR(@;u`_bctLq=hIg_~L)X>pKVGQ{`9N=XuY7M9Z+-rd zUfjWbf{RO@S2i~{e|$OiF4v}3Dr-yK7D2(M^auW0nyZ!mT0-TZH`ZtZ(o6n>LRU4U z1L;R|eoqS+NS?X>{2!!$XnNMa^kdLK?$dE}Rv`SDVp(T2XuZ@TS>RtU3BDS`c*=cR zU`HX%DhFAnfU|O#lg!q(yb`CIo790kU9Sv2(h3FIR!M!#*$W6Jn#t-3@j^8X^9kFa z95WKM{VD`bU)0e0i7da{jDX2}o`Zp}SXn%)&+YuX;QKm0B@zw!ZHs-f_Uiz!-!N?P zlZQF*yP>|}VDmsex1BQ;06yND`FTLioh*7&CyP8Iz?k#?~`x9E^yQV%8nAUNZ1@5#)bPl%5?3&D&?6SI`)vma-Gj3@YD z-0aRyLQR7O+LiNks|M)iVr>mPu<{%vr1E$)9ggC_N=B?aCmHy}dOZNW|$1@m^G@qzfI5qfQJX_+B=fXL~zZlcA_a{9c5uJdZ%5v zJO8$$3JjBuohyY?Asqug-nhyEpE=Y-hqNH@W13GKhVi21g)cLP&PjcEX<)5{7nnEz z&`iY02;)6eRQ|0`o<6GkZ)n^B-GWtYdYu>f75;tT@b|fjW0mP=t>}Op`G|_~8E&PGycy@d z5gzPaAL9m*jtPf0`@c+L3*+@;&za)!5F!}Bq=KLheciHC;pF2iy8Wb*!r+6WLfG{& zi8^S__?Ot`ZKNCRNi?UtCgzUIaQJ*(=T}>Q%_*5bKQQ(utM#GPIr+@4{*ti=&j_1R zCgxTSgtKb&zZD7nNt3`Al#`w}=f%`~6&qnf-HI|6gh`84GUwu>1Kw^>mi5?vyw!%4 z0(lb8W}Y4=R8f556d!h)82J#g-s=7MqFblo9~S6B84|xW6;i*gG;CA;XT0%V)7X;! zd8R|`6rYL5UI5IDeCUc+A z&IDIJ<mS!3n` zMI{HT6tRm-NLbl43Os}BnUrHktjfwg37equw7+tok$c~NQrU|PGqY(@VLlNnX;Q4M zshraftfZlnn#-UIY|_l0wxKALKE^UJ9=&+8580)FYxZ(@b~3Z&K0L|VP+ayUW;U3a zc@Ix&3xDIdYkNCTU}h#+Qo2V1mH|+=Iv=}}JD)5RVH3Gk^{S_KDI!56J7+g+B&c82 zQMCCMb3^H@t6V@B? z3iA^H5xxrADx@c{8T~{j?geq@T&Kw(W^e!5%)kW%%MX_9|lbN9=^ zMPH3Cm|n((N`fKmevEe75c0f>$`%eL1c4A(s8Ro22LP>2o3M8(C?xr|uny)px;uAf z$-mE;b33V<7QL0^l+WFDH^f6-H-+}Dhi6)7s*C!w1%-AGB02vEvpv3-%YtHFQnp)O zUEWS>w4l)nX;~X#(_S|uSwzrkWrOlLI&O?#7ptm3tf{|OB9D^Di(V^@wFin!tnxvnBf`e-ibsBAN^-ySt?Ur7feN4SPU_`V*r$nxSebxoj;P zQSwh9p>mNXjJ6(dt|3>o9t#n+``Ay6VOp(KR}sh4PSZDh$qm~Nx=#p1);EqQj$0cb zib1_r_7bpH^J>jxZGn^GK=siUSyQP?u}YVZnCo3H28Whi#(sOiRK0Osx-vBZ-WOzc zZjC@=w5p{cwCu+$F5w~KgaUWjXZwk0D%Vtoyn<_qrTt;MgTRacOkEX3<1sMMtcWI5 zrx^OZZ5s)dis&nmZ;rJgU!|>Bx+>E4)L~y5x)C7J;!AC8X>2J?cbuOOk6zWzJIF)VnQcV-pu8l^EW{$&5k%)OE*I%;o8G(5_Li;_Tns*D#bkQA?ia{SzX=dUIa8%+qvL zRpE!6oBm{UXl+boR@)K$xov~DXw<}*+DqRb$wqgaLGDSg z5z*@uww-H#R3UJ!ywbEXsJx=OVBHBc_jVoj(QRH zo=R?;(ct0~SVnrBWrpR8-H)Mu5^L~R`YF+6=SC{?^-g72!XCeWNRH$0YpecAKf(<) z$}f>PAYmu_>}IshQ^gz$qrL3Wsml)i!yum0vV^9FUFkZwQHp8OFgl zM#s;}(YFa=!Ipl?f~Rl;rhOBHh!Yrrx3%F!ote>*df0*oQx63etcr?b?3}LM&?Q14 z?|GmF>6eX1?-rj*IBfLxaqh7pfsshZ(SQ6du+z9LrCqvo-H9a`0nT*j_ZMY$+h9EPF*asNgDUARSsiL@I-lURZ05$=xKZyppChg9NZn%9sj z2IcTIhk{x*Q;s}2D1+o6@(XE5-`JgVIqcl(I+a(0?Z#=pd3Pu0Jm42p3aYiUyjXjI z4_DwvDV2otQ~UQQ^c!7|ZajmIL*LGn2z%E2m-6*PbgQ-LwS#j*`G?<*`n-AdM$#YjG4OYmajt`A1Vym15MI%A?0|N=Z0N+vHJ(`K9C`8ChKM`4BHsw*W{LmqdJ@ z3S`Tx%K(t)RH5rtJ^yNBO7TumzxA)70#?>57iGMyuKJ#)JN)z~HA0CmAItU$KG=y^Q_+HzWl)^v4}F6i{T0b`9y8vO_dP+^^JY8qnZ01s0rDanZ4+8?lX7) z;=j;MbpaEPv*W!{TgUI4$CfA@Wmv^u#wf(Y!)$v@=gTRcuYcsd!4?8t;IdifPmG38 zZ`E-Z`S5f(G_&sq+b`Tkt#1`;IM&BKJ$r7y!6L0CK>5JUuzdJ@dn1ba*TDzJ0SPHB z|9c1(AoHiv1_y`J(PzV8w>BIUaVGmSj%Ohh*p<9-aY0N+2Oqvw*hURRrNQ6OnNET{ z-yvQq0P5oK+=?N>iavL)FH}|XkpM3?!tQ_qyN+kH@dGvTlzp5E{q#y>OMxPSJUYn9 zgBV)68_c&+%lv6{$v2a-I0J97B9}qviGHB{k6!zVNa{1F`_pj2~#9a=-!AZ z@);Y1UF!GOg&&uoF77+aS(1SD=X+*K0wV9sOvI4_)mLjB;k&#jRnMfJ^0$*puO^yeeA%mr7hS2e2^Xyq}!BT$!QuPDDUEVMjI;;D0 zUuZ!i3F_k5|BM)n@?7*giiYB0>YqLa;YLQddxx4}T1-T_lDY${A+rp9 zWSzmlmv2rW7pI_ScQ78GAU$Q#&zO-19T}esS?1bNWPm+4? z-E(Gz4B z&9xW-e_eS?iCooY`<&%&)?(eoLQsndieqAr7}UdUi=(7h8hR!d&en`a&_`_+LNYdK zOq}a{!nQlXZ$FW$fH<5+1cNQGz*4 zq%~Sn-a3vu4PX89n&_$r>6Z;~%obg@UYhcmY0#I{4l!_(Mk^p)eEq|9rAhJdQzN>B zKFXjQ8qrtL<@XP-f2N}r^AY%%JnGBtD9xUPY<4h?1*_=LKZ0*6ul=K?wlo~!X#}l*1`bHoofc)bEF+@VL=;|`x|tsY z_PdX+pLAS!{QPb677K2=d^v$_W7DzHV;LUKlIy(s6vq7(Jx(NXbl%BQdJ?8bY9LDm zCo{JT(_=Q2PUtFOL*G8aGxgvMpJp%#sclhHSusTCn|%_dr*Y0^gB4rZW|0KOx@cnD z3Faz2yLSBL`oO}O8d!mqpycoKCVSm8-1i>-W#`CGaKUn8J={lU7Kby_kCE>p$#2`Z zF0COT5c<6Ha2lg{6Ny&bN3Yq?CVTAZ_u)+OoZT}b3nb4?As(|;A%@ZBHCCx~uZs~2 zVlc}ZE9u6758)Emz9$EdNOLr`^##baM$1R_^^E~r`Yh-UF7#3URXI;XUcKGNh3K!? z>gWu=X@(Ul814BZ%GTcV?EioW*XgRfMBXI{irlm0((fsx-+CPWI9$H?iIV9-)7v>5 z3Ul)%mGb>MSWyP1(BxTX@YLMYf5lS0eyw^+5Ebj^cZK7=1#m|QX9`Z)Z;!0z#zJ6a zKK9BaE4j0dwzAZQ37NLr`Ttf_MBg_8s5{yi43@pOMjqBCHG|#FcEBm}W1~ACh52tQ4 zc&cWC4Lf28sd-=6A1pTlBSc7!x8e&i#o4FAi#NX=W|Qfi&A@^XahQWOuR zTsjkYC`xG>uOgdMR-AFn-xQv|sZ?LwIB?Da%RQ3>%Ho<&>${w~Ri^KfQ>(1TwL*2m z+#^=O99OPAzb@ULvF3^`*67aX0~49BZ4Xl32Nnz(+RxpHRMaeh*QoIEB8Pr27`Fy@h(|xr`@1nC^t9|70&$on~EwxLw+zTj7f_GDwYU zzVQ{~Ub#dI5t9y)m_M78Ec7Qi`saZyLHe;U?l?iSz{#$8N4aH2?p&*we$&Ka@n9U2 z`#&d`U+6l?{{v!mv9QlONrqI8V|?IH1V03*irzRJKJ3e34AnxodE5NXWh8-Z*n6w{)h;|DiWWM3sEor;Qrz>{*$ZI ziYsLLFX~ugA>oq=K$Clotya`h$llFX%GOwIL|u1>B7;6NQ2|v?`K9*|?YdqvY|WU4 z6buIuci7x=6v9_QvHuh(^tV0xN3a*5v%c+=K?z%ybB9I8v`=X=ez%o-knD+oK56-u zHz_d@vuv(s-g4q zeE?5Y`sRRXdwQMWIWRwl!xuceF@?0%oP5CiW#mn}xLplX%9A|Cl=&bt{WY`JlOZVib_R{V;P&F zVTEgon<-aT;cY_|9250R6>pfx-^vU6NL0j_S|L}JaL9bX%(ucOH5XtIPH18@D9pi~ z7GKKIZp~s&u#I!AN7&nx4STs}Ub;74(1Pcz|4QS$oba;lEZKG;>qA-j#}{+RUiVZS zw_PRtu++`NPv^F-p+yjP8i?qqJ$d{qdsRwiYv1UPN8{!{3?bRGrlB4^qKnhuppPYG zG(+3$?c;I;mph9M17~G2ctVE+rK!Zc7o*N1g)GSZUP}&eV z3m-Lt$`kxJR930(*c;>E*THrZg-(i0UH82E-KO_5Y<%UQ4~?qNMbOsU@XdxkDH=Hj zn+(%P$4$&#bbo=A+g7F~Ze`H~L~7pg8xsH~4WeV4gy)#3aN?GoZ485sxMk07*;4F* z1awfpV2>Lc6T!N}CSJ%oY9<@mDEiP(j@G17OcVgHsYJyl9ys3mws!gvyCpKe_*4wu z$@G?#=}uSx{#aHhsi+5qN+c z&x3x-1D`XV;|v3+%>FyVgmaHtL&lC{ND3$RX$Q>pvnu^aULMM+Fn1*^zy za0yMZuBlx%_0p5t=8Qt3Jtm{ui7et_PrPrN)Xa&(q^V9QqPrYz!UpLY5nHb-B*2 zJYetmY!}hU2$NK$vdsCe2L`h2$nC`vDQoGF9hQuSgz)v~uibo#Lr1yJMQuOdCFXj# z!v3Pvj&LLt|0&ovDi~jK7mQaa_rY2Mo&~;_IUarP1B*oWJnaK$vhEuX;PCuC` zGV|;|=yY-RV0|;Q*W7PqSNAIT^6WD(iJg8%;`w}E&+3)QEQr(yGYkULE>!9Ev>o3nf*SlCOqNd14>1FUD z=n{ODfM^ntiz`pBra z7_Xdj&)}q;ENm{(vpR^RH)K1|G*{9NkA~lMMTI*ImJRxkOJlY0DTuxv80Me~#Wq?E z5)qt1am|gc0#!wH?| zk8J8qioaQEfxQxd-Fb%8a>p(vX$Iq@s3FLUF*=(cQig6IvJ(0uCT~xvhOmrE8Yz zyAJt}#Fd(i==aPYY^b16B|a0>F0O9foOcC|E;f{8z+9kYwUS= z(B-jygZ%;v4lCcP#_J=yVjAaFVJVPR^mH#VUfX#<8bHBy?W$H>YGA~5_xA7Qbu^Ia zd1o3kOP9!S<53YKx+BdaY;)18=&$a6`$zS@kc9_%@+zzPYRbAJy+0Ixq?BpkG2eRW zC&+6x4jjUv!EY2vp-X^cUlr-M@L-AbV1K4h4sGi*m7*M;FpY!SJ&mTVl>*Sh0Sb5x z1G&B-qrL%ObKIBInoU{b1ry6{m_r)EtTBzgwwRLR>k9FO14@w{DW6IYY=rG;`5m{Y zZNCkEb)genTe!iDdTpWPR8@toQ)kVpt9e;7Rql6_1N*I8IYPgikX9*NHcR%7^3`d7 zEH}+n_fqQJK<6H24CS;e<9vUASsuhrWLXSqUrBb}a{;gq{+2@|#Db2eWiJs*!llmJ!-s0k=Wx5MMu^cimC*Dj5HfR`#8kbVXeOK@A*@n^&MP!EXwnr)H#PS zaR195tp+fy+-Mp6htF68|5X5E{jfauzY1WviWxKbmw2?w%kJ1oykhI_%#Qjtn-qaE z6(QUEs|!6!GYQ3BS7cnPEXhTIuV8FG2gG2yJ3D74OV=)I<~s-MU?W{}tjrMS$lI^v#zrnFT%zWa^s)mg=BmmSQ02=9?jXo&;Z2G; zRSkO`{k6WU9ryQwZ;ih(uWE@`HOIM8&@Wp%bc6pKcW~QbP|kGjpqwcz(hPw@_wLHo z9EjC^8@O;JizJ~>7AQj%h*sWukUq>%&eFPQf6AGsKf58<@Az#>7C%%1-I8!-ect|u zD%0^P3BB=9#&A8g zN4QDC84EOnato4hivF@1Tx9{9LP5s`D4qsZD;DCbSa?R1zzy04HeTE=2m2Dg%HR25 zt*9b4VLOdKOuvAB+ zu3e$5j*VQ`&Z;JWSE^6p9lE>kKY!&FR}?J0y09hl0Mu=lsAb3LT4QdY`Ii#!;`ub zP2ucX7Pg(W(IvfK{g4uU+Y0kCpk2m(7)y9mytz83~1EoW(;@TBk|Hbmw%0Ok606=2-A;1d6rhxt&#Tt57{#Ku1_Hxyeqa~9V<0LtsYh~ z7$e$s4-$&tmx{e_xu{&Swd>?WDvhGl{=hf%WVY9I4SWMVvmGBMye{CP;qdgne#p-7 zVy*lu1_vY_as=@#dh?*66vj~*DwipxM~YKB3$U7(8t@oF;fEY<8H><>o*Q=V2GHN> z{ - - diff --git a/app/src/main/res/drawable/ic_account.xml b/app/src/main/res/drawable/ic_account.xml deleted file mode 100644 index 9e11be41..00000000 --- a/app/src/main/res/drawable/ic_account.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_add_account_grey.xml b/app/src/main/res/drawable/ic_add_account_grey.xml deleted file mode 100644 index d5d51b07..00000000 --- a/app/src/main/res/drawable/ic_add_account_grey.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_add_white.xml b/app/src/main/res/drawable/ic_add_white.xml deleted file mode 100644 index e3979cd7..00000000 --- a/app/src/main/res/drawable/ic_add_white.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_cancel_grey.xml b/app/src/main/res/drawable/ic_cancel_grey.xml deleted file mode 100644 index 12c616ef..00000000 --- a/app/src/main/res/drawable/ic_cancel_grey.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_check_green.xml b/app/src/main/res/drawable/ic_check_green.xml deleted file mode 100644 index 123c5bb0..00000000 --- a/app/src/main/res/drawable/ic_check_green.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml deleted file mode 100644 index 39e64d69..00000000 --- a/app/src/main/res/drawable/ic_delete.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_delete_grey.xml b/app/src/main/res/drawable/ic_delete_grey.xml deleted file mode 100644 index 8561676b..00000000 --- a/app/src/main/res/drawable/ic_delete_grey.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml deleted file mode 100644 index 15b7a280..00000000 --- a/app/src/main/res/drawable/ic_edit.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_edit_grey.xml b/app/src/main/res/drawable/ic_edit_grey.xml deleted file mode 100644 index d1f47502..00000000 --- a/app/src/main/res/drawable/ic_edit_grey.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_empty_star.xml b/app/src/main/res/drawable/ic_empty_star.xml deleted file mode 100644 index 26a48774..00000000 --- a/app/src/main/res/drawable/ic_empty_star.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_error.xml b/app/src/main/res/drawable/ic_error.xml deleted file mode 100644 index 26d5a65e..00000000 --- a/app/src/main/res/drawable/ic_error.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_filter.xml b/app/src/main/res/drawable/ic_filter.xml deleted file mode 100644 index 5d4ec18e..00000000 --- a/app/src/main/res/drawable/ic_filter.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_folder_grey.xml b/app/src/main/res/drawable/ic_folder_grey.xml deleted file mode 100644 index f27a93ac..00000000 --- a/app/src/main/res/drawable/ic_folder_grey.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_import_export.xml b/app/src/main/res/drawable/ic_import_export.xml deleted file mode 100644 index 99f71b7f..00000000 --- a/app/src/main/res/drawable/ic_import_export.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_new_folder.xml b/app/src/main/res/drawable/ic_new_folder.xml deleted file mode 100644 index 43d025db..00000000 --- a/app/src/main/res/drawable/ic_new_folder.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_notif.xml b/app/src/main/res/drawable/ic_notif.xml deleted file mode 100644 index 4366e94e..00000000 --- a/app/src/main/res/drawable/ic_notif.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/ic_notifications.xml b/app/src/main/res/drawable/ic_notifications.xml deleted file mode 100644 index 6c01015e..00000000 --- a/app/src/main/res/drawable/ic_notifications.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_open_in_browser.xml b/app/src/main/res/drawable/ic_open_in_browser.xml deleted file mode 100644 index f416ca58..00000000 --- a/app/src/main/res/drawable/ic_open_in_browser.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_open_in_browser_white.xml b/app/src/main/res/drawable/ic_open_in_browser_white.xml deleted file mode 100644 index 3fb9799c..00000000 --- a/app/src/main/res/drawable/ic_open_in_browser_white.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_read.xml b/app/src/main/res/drawable/ic_read.xml deleted file mode 100644 index f26dd5e2..00000000 --- a/app/src/main/res/drawable/ic_read.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_read_later.xml b/app/src/main/res/drawable/ic_read_later.xml deleted file mode 100644 index c1c86f62..00000000 --- a/app/src/main/res/drawable/ic_read_later.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_reading_time.xml b/app/src/main/res/drawable/ic_reading_time.xml deleted file mode 100644 index ef0c47e6..00000000 --- a/app/src/main/res/drawable/ic_reading_time.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml deleted file mode 100644 index cc2d1e04..00000000 --- a/app/src/main/res/drawable/ic_refresh.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_rss_feed_grey.xml b/app/src/main/res/drawable/ic_rss_feed_grey.xml deleted file mode 100644 index fedffd69..00000000 --- a/app/src/main/res/drawable/ic_rss_feed_grey.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_select_all_white.xml b/app/src/main/res/drawable/ic_select_all_white.xml deleted file mode 100644 index bc75904c..00000000 --- a/app/src/main/res/drawable/ic_select_all_white.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml deleted file mode 100644 index f7c249c4..00000000 --- a/app/src/main/res/drawable/ic_settings.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_share_white.xml b/app/src/main/res/drawable/ic_share_white.xml deleted file mode 100644 index 045bbc0c..00000000 --- a/app/src/main/res/drawable/ic_share_white.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_star.xml b/app/src/main/res/drawable/ic_star.xml deleted file mode 100644 index c172fe62..00000000 --- a/app/src/main/res/drawable/ic_star.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_sync.xml b/app/src/main/res/drawable/ic_sync.xml deleted file mode 100644 index f16a1b06..00000000 --- a/app/src/main/res/drawable/ic_sync.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_timeline.xml b/app/src/main/res/drawable/ic_timeline.xml deleted file mode 100644 index 68d26c91..00000000 --- a/app/src/main/res/drawable/ic_timeline.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_unread.xml b/app/src/main/res/drawable/ic_unread.xml deleted file mode 100644 index 7b0203d3..00000000 --- a/app/src/main/res/drawable/ic_unread.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_warning_red.xml b/app/src/main/res/drawable/ic_warning_red.xml deleted file mode 100644 index 745c0e76..00000000 --- a/app/src/main/res/drawable/ic_warning_red.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/item_date_background.xml b/app/src/main/res/drawable/item_date_background.xml deleted file mode 100644 index 5b493614..00000000 --- a/app/src/main/res/drawable/item_date_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/logo.png b/app/src/main/res/drawable/logo.png deleted file mode 100644 index 720489198b7939ed4f986308ee9e675b34b505c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7915 zcmV004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_indicator.xml b/app/src/main/res/drawable/tab_indicator.xml deleted file mode 100644 index f1e295c6..00000000 --- a/app/src/main/res/drawable/tab_indicator.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/toolbar_scrim.xml b/app/src/main/res/drawable/toolbar_scrim.xml deleted file mode 100644 index 0d65b59f..00000000 --- a/app/src/main/res/drawable/toolbar_scrim.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/account_type_item.xml b/app/src/main/res/layout/account_type_item.xml deleted file mode 100644 index be5446c3..00000000 --- a/app/src/main/res/layout/account_type_item.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_account_type_list.xml b/app/src/main/res/layout/activity_account_type_list.xml deleted file mode 100644 index 8ce3e3a1..00000000 --- a/app/src/main/res/layout/activity_account_type_list.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - -