├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── android.yml ├── .gitignore ├── .gitmodules ├── LICENSE.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── debug │ └── res │ │ ├── drawable │ │ ├── launcher_background.xml │ │ └── launcher_foreground.xml │ │ ├── mipmap-hdpi │ │ └── launcher.png │ │ ├── mipmap-mdpi │ │ └── launcher.png │ │ ├── mipmap-xhdpi │ │ └── launcher.png │ │ ├── mipmap-xxhdpi │ │ └── launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── launcher.png │ │ └── values │ │ └── consts.xml │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── database │ │ ├── 000.sql │ │ ├── 017.sql │ │ ├── 019.sql │ │ ├── 020.sql │ │ ├── 021.sql │ │ └── 022.sql │ ├── java │ └── com │ │ └── tughi │ │ └── aggregator │ │ ├── App.kt │ │ ├── AppActivity.kt │ │ ├── Constants.kt │ │ ├── Coroutines.kt │ │ ├── Notifications.kt │ │ ├── activities │ │ ├── backup │ │ │ └── BackupActivity.kt │ │ ├── cleanupmode │ │ │ ├── CleanupModeActivity.kt │ │ │ ├── CleanupModeAdapter.kt │ │ │ ├── CleanupModeViewHolders.kt │ │ │ └── Extensions.kt │ │ ├── entrytagrulesettings │ │ │ ├── DeleteEntryTagRuleDialogFragment.kt │ │ │ └── EntryTagRuleSettingsActivity.kt │ │ ├── entrytags │ │ │ └── EntryTagsActivity.kt │ │ ├── feedentrytagrules │ │ │ └── FeedEntryTagRulesActivity.kt │ │ ├── feedsettings │ │ │ ├── FeedSettingsActivity.kt │ │ │ ├── FeedSettingsFragment.kt │ │ │ └── UnsubscribeDialogFragment.kt │ │ ├── feedspicker │ │ │ └── FeedsPickerActivity.kt │ │ ├── main │ │ │ ├── EntriesFragment.kt │ │ │ ├── EntriesFragmentAdapterListener.kt │ │ │ ├── EntriesFragmentEntryAdapter.kt │ │ │ ├── EntriesFragmentViewHolder.kt │ │ │ ├── EntriesFragmentViewModel.kt │ │ │ ├── EntriesRecyclerView.kt │ │ │ ├── FeedEntriesFragment.kt │ │ │ ├── FeedEntriesFragmentViewModel.kt │ │ │ ├── FeedsFragment.kt │ │ │ ├── FeedsFragmentFeedAdapter.kt │ │ │ ├── FeedsFragmentFeedAdapterListener.kt │ │ │ ├── FeedsFragmentFeedViewHolder.kt │ │ │ ├── FeedsFragmentViewModel.kt │ │ │ ├── MainActivity.kt │ │ │ ├── MyFeedFragment.kt │ │ │ ├── TagEntriesFragment.kt │ │ │ └── TagsFragment.kt │ │ ├── myfeedsettings │ │ │ ├── MyFeedSettingsActivity.kt │ │ │ └── MyFeedSettingsFragment.kt │ │ ├── notifications │ │ │ └── NewEntriesActivity.kt │ │ ├── opml │ │ │ ├── OpmlExportActivity.kt │ │ │ ├── OpmlFeedViewHolder.kt │ │ │ ├── OpmlFeedsAdapter.kt │ │ │ ├── OpmlFeedsFragment.kt │ │ │ ├── OpmlFeedsViewModel.kt │ │ │ └── OpmlImportActivity.kt │ │ ├── optionpicker │ │ │ ├── Extensions.kt │ │ │ ├── Option.kt │ │ │ └── OptionPickerActivity.kt │ │ ├── reader │ │ │ ├── ReaderActivity.kt │ │ │ ├── ReaderFragment.kt │ │ │ └── ReaderWebView.kt │ │ ├── subscribe │ │ │ ├── SubscribeActivity.kt │ │ │ ├── SubscribeFeedFragment.kt │ │ │ ├── SubscribeFeedFragmentViewModel.kt │ │ │ ├── SubscribeSearchFragment.kt │ │ │ ├── SubscribeSearchFragmentAdapter.kt │ │ │ ├── SubscribeSearchFragmentAdapterListener.kt │ │ │ ├── SubscribeSearchFragmentViewHolder.kt │ │ │ └── SubscribeSearchFragmentViewModel.kt │ │ ├── tagsettings │ │ │ ├── DeleteTagDialogFragment.kt │ │ │ └── TagSettingsActivity.kt │ │ ├── tagspicker │ │ │ └── TagsPickerActivity.kt │ │ ├── theme │ │ │ ├── ThemeActivity.kt │ │ │ └── ThemeDialogFragment.kt │ │ ├── updatemode │ │ │ ├── Extensions.kt │ │ │ ├── UpdateModeActivity.kt │ │ │ ├── UpdateModeAdapter.kt │ │ │ └── UpdateModeViewHolders.kt │ │ └── updatesettings │ │ │ ├── UpdateSettingsActivity.kt │ │ │ └── UpdateSettingsFragment.kt │ │ ├── data │ │ ├── CleanupMode.kt │ │ ├── ContentValues.kt │ │ ├── Database.kt │ │ ├── Entries.kt │ │ ├── EntryTagRules.kt │ │ ├── EntryTags.kt │ │ ├── Feeds.kt │ │ ├── MyFeedTags.kt │ │ ├── Query.kt │ │ ├── Repository.kt │ │ ├── Tags.kt │ │ └── UpdateModes.kt │ │ ├── entries │ │ ├── EntryTagRuleHelper.kt │ │ └── conditions │ │ │ ├── Condition.kt │ │ │ ├── Expression.kt │ │ │ ├── Parser.kt │ │ │ ├── Token.kt │ │ │ └── Tokenizer.kt │ │ ├── feeds │ │ ├── FeedDateParser.kt │ │ ├── FeedParser.kt │ │ ├── FeedsFinder.kt │ │ ├── OpmlFeed.kt │ │ ├── OpmlGenerator.kt │ │ └── OpmlParser.kt │ │ ├── ion │ │ ├── AggregatorData.kt │ │ ├── CustomElement.kt │ │ ├── Entry.kt │ │ ├── EntryTag.kt │ │ ├── EntryTagRule.kt │ │ ├── Feed.kt │ │ ├── MyFeedTag.kt │ │ └── Tag.kt │ │ ├── preferences │ │ ├── EntryListSettings.kt │ │ ├── MyFeedSettings.kt │ │ ├── UpdateSettings.kt │ │ └── User.kt │ │ ├── services │ │ ├── AutoUpdateScheduler.kt │ │ ├── AutoUpdateService.kt │ │ ├── BackupService.kt │ │ ├── FaviconUpdateHelper.kt │ │ ├── FaviconUpdateScheduler.kt │ │ ├── FaviconUpdateService.kt │ │ └── FeedUpdateHelper.kt │ │ ├── utilities │ │ ├── Browser.kt │ │ ├── Favicons.kt │ │ ├── Html.kt │ │ ├── Http.kt │ │ ├── Language.kt │ │ ├── List.kt │ │ ├── Result.kt │ │ ├── Sharing.kt │ │ └── URL.kt │ │ └── widgets │ │ ├── BottomSheetOption.kt │ │ ├── DropDownButton.kt │ │ ├── EntryItemLayout.kt │ │ └── InlineImageView.kt │ ├── res │ ├── anim │ │ ├── fade_in.xml │ │ ├── fade_out.xml │ │ └── slide_in.xml │ ├── color │ │ ├── bottom_navigation_item.xml │ │ └── dropdown_drawable.xml │ ├── drawable-anydpi-v24 │ │ └── notification.xml │ ├── drawable-hdpi │ │ └── notification.png │ ├── drawable-mdpi │ │ └── notification.png │ ├── drawable-xhdpi │ │ └── notification.png │ ├── drawable-xxhdpi │ │ └── notification.png │ ├── drawable │ │ ├── action_add.xml │ │ ├── action_add_rule.xml │ │ ├── action_back.xml │ │ ├── action_backup.xml │ │ ├── action_cancel.xml │ │ ├── action_check.xml │ │ ├── action_delete.xml │ │ ├── action_feeds.xml │ │ ├── action_forum.xml │ │ ├── action_invert_selection.xml │ │ ├── action_mark_all_done.xml │ │ ├── action_mark_done.xml │ │ ├── action_mark_pinned.xml │ │ ├── action_menu.xml │ │ ├── action_my_feed.xml │ │ ├── action_palette.xml │ │ ├── action_refresh.xml │ │ ├── action_search.xml │ │ ├── action_settings.xml │ │ ├── action_show_less.xml │ │ ├── action_show_more.xml │ │ ├── action_star.xml │ │ ├── action_star_empty.xml │ │ ├── action_tags.xml │ │ ├── action_update_settings.xml │ │ ├── action_warning.xml │ │ ├── arrow_drop_down.xml │ │ ├── check_box_checked.xml │ │ ├── check_box_unchecked.xml │ │ ├── entry_state_pinned.xml │ │ ├── entry_state_starred.xml │ │ ├── favicon_aggregator.xml │ │ ├── favicon_person.xml │ │ ├── favicon_pin.xml │ │ ├── favicon_placeholder.xml │ │ ├── favicon_rule.xml │ │ ├── favicon_star.xml │ │ ├── favicon_tag.xml │ │ ├── launcher_background.xml │ │ ├── launcher_foreground.xml │ │ ├── launcher_monochrome.xml │ │ ├── radio_button_checked.xml │ │ ├── radio_button_unchecked.xml │ │ ├── theme_accent_blue.xml │ │ ├── theme_accent_green.xml │ │ ├── theme_accent_orange.xml │ │ ├── theme_accent_purple.xml │ │ ├── theme_accent_red.xml │ │ ├── theme_bottom_navigation_accent.xml │ │ ├── theme_bottom_navigation_gray.xml │ │ ├── theme_dark.xml │ │ └── theme_light.xml │ ├── layout │ │ ├── backup_activity.xml │ │ ├── cleanup_mode_activity.xml │ │ ├── cleanup_mode_item_checked.xml │ │ ├── cleanup_mode_item_checked_age_1_month.xml │ │ ├── cleanup_mode_item_checked_age_1_week.xml │ │ ├── cleanup_mode_item_checked_age_1_year.xml │ │ ├── cleanup_mode_item_checked_age_3_days.xml │ │ ├── cleanup_mode_item_checked_age_3_months.xml │ │ ├── cleanup_mode_item_checked_age_3_years.xml │ │ ├── cleanup_mode_item_checked_age_6_months.xml │ │ ├── cleanup_mode_item_checked_age_6_years.xml │ │ ├── cleanup_mode_item_checked_default.xml │ │ ├── cleanup_mode_item_checked_never.xml │ │ ├── cleanup_mode_item_unchecked.xml │ │ ├── drop_down_button.xml │ │ ├── entry_list_divider.xml │ │ ├── entry_list_entry_placeholder.xml │ │ ├── entry_list_fragment.xml │ │ ├── entry_list_header.xml │ │ ├── entry_list_read_entry.xml │ │ ├── entry_list_unread_entry.xml │ │ ├── entry_tag_rule_settings_activity.xml │ │ ├── entry_tag_rules_item.xml │ │ ├── entry_tags_activity.xml │ │ ├── entry_tags_item.xml │ │ ├── feed_entry_tag_rules_activity.xml │ │ ├── feed_settings_fragment.xml │ │ ├── feeds_fragment.xml │ │ ├── feeds_item_collapsed.xml │ │ ├── feeds_item_expanded.xml │ │ ├── feeds_picker_activity.xml │ │ ├── feeds_picker_item__multiple_choice.xml │ │ ├── feeds_picker_item__single_choice.xml │ │ ├── main_activity.xml │ │ ├── my_feed_settings_fragment.xml │ │ ├── opml_export_activity.xml │ │ ├── opml_fragment.xml │ │ ├── opml_import_activity.xml │ │ ├── opml_import_list_item.xml │ │ ├── option_picker_activity.xml │ │ ├── option_picker_item_checked.xml │ │ ├── option_picker_item_unchecked.xml │ │ ├── reader_activity.xml │ │ ├── reader_entry_fragment.xml │ │ ├── subscribe_feed_fragment.xml │ │ ├── subscribe_feed_item.xml │ │ ├── subscribe_loading_item.xml │ │ ├── subscribe_message_item.xml │ │ ├── subscribe_search_fragment.xml │ │ ├── tag_settings_activity.xml │ │ ├── tag_settings_activity__name.xml │ │ ├── tag_settings_activity__rule.xml │ │ ├── tag_settings_activity__user.xml │ │ ├── tags_fragment.xml │ │ ├── tags_item_collapsed.xml │ │ ├── tags_item_expanded.xml │ │ ├── tags_picker_activity.xml │ │ ├── tags_picker_item__multiple_choice.xml │ │ ├── tags_picker_item__single_choice.xml │ │ ├── theme_dialog.xml │ │ ├── update_mode_activity.xml │ │ ├── update_mode_item_checked.xml │ │ ├── update_mode_item_checked_adaptive.xml │ │ ├── update_mode_item_checked_default.xml │ │ ├── update_mode_item_checked_disabled.xml │ │ ├── update_mode_item_checked_on_app_launch.xml │ │ ├── update_mode_item_checked_repeating.xml │ │ └── update_mode_item_unchecked.xml │ ├── menu │ │ ├── bottom_navigation.xml │ │ ├── cleanup_mode_activity.xml │ │ ├── entry_list_fragment.xml │ │ ├── entry_tag_rule_settings_activity.xml │ │ ├── entry_tags_activity.xml │ │ ├── feed_entry_tag_rules_activity.xml │ │ ├── feed_list_fragment.xml │ │ ├── feed_settings_fragment.xml │ │ ├── feeds_picker_activity.xml │ │ ├── my_feed_fragment.xml │ │ ├── opml_feeds_fragment.xml │ │ ├── option_picker_activity.xml │ │ ├── reader_activity.xml │ │ ├── tag_settings_activity.xml │ │ ├── tags_fragment.xml │ │ ├── tags_picker_fragment.xml │ │ └── update_mode_activity.xml │ ├── mipmap-anydpi-v26 │ │ └── launcher.xml │ ├── mipmap-hdpi │ │ └── launcher.png │ ├── mipmap-mdpi │ │ └── launcher.png │ ├── mipmap-xhdpi │ │ └── launcher.png │ ├── mipmap-xxhdpi │ │ └── launcher.png │ ├── mipmap-xxxhdpi │ │ └── launcher.png │ ├── raw │ │ └── entry.html │ ├── values │ │ ├── anim.xml │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── consts.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── xml │ │ └── update_settings.xml │ └── svg │ ├── action_add_rule.svg │ ├── action_feeds.svg │ ├── action_invert_selection.svg │ ├── action_my_feed.svg │ ├── action_tags.svg │ ├── action_update_settings.svg │ ├── favicon_aggregator.svg │ ├── favicon_person.svg │ ├── favicon_pin.svg │ ├── favicon_placeholder.svg │ ├── favicon_rule.svg │ ├── favicon_star.svg │ ├── favicon_tag.svg │ ├── launcher.svg │ ├── launcher_background.svg │ ├── launcher_monochrome.svg │ └── notification.svg ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── metadata └── en-US │ ├── changelogs │ ├── 299031.txt │ ├── 299032.txt │ ├── 299033.txt │ └── 299034.txt │ ├── full_description.txt │ ├── images │ ├── icon.png │ └── phoneScreenshots │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ └── 5.png │ ├── short_description.txt │ └── title.txt └── settings.gradle /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Device (please complete the following information):** 17 | - Aggregator version: [e.g. Preview-028] 18 | - Device: [e.g. Google Pixel 3a] 19 | - Android version: [e.g. Android 9] 20 | 21 | **Screenshots** 22 | If applicable, add screenshots to help explain your problem. 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | submodules: true 17 | 18 | - name: Set up JDK 17 19 | uses: actions/setup-java@v3 20 | with: 21 | java-version: 17 22 | distribution: temurin 23 | 24 | - name: Build with Gradle 25 | run: ./gradlew --no-daemon build 26 | 27 | metadata: 28 | needs: build 29 | 30 | runs-on: ubuntu-latest 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - uses: ashutoshgngwr/validate-fastlane-supply-metadata@v2 36 | with: 37 | fastlaneDir: ./metadata 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /local.properties 3 | /*.jks 4 | 5 | /.gradle 6 | 7 | /.idea 8 | /*.iml 9 | 10 | /databases 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "sax-framework"] 2 | path = sax-framework 3 | url = https://github.com/tughi/sax-framework.git 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Android CI](https://github.com/tughi/aggregator-android/actions/workflows/android.yml/badge.svg?branch=master)](https://github.com/tughi/aggregator-android/actions/workflows/android.yml) 2 | 3 | # Aggregator 4 | 5 | > Available on [Google Play](https://play.google.com/store/apps/details?id=com.tughi.aggregator.next) and [F-Droid](https://f-droid.org/packages/com.tughi.aggregator/). 6 | 7 | More details are available [here](https://tughi.github.io/aggregator-android). 8 | 9 | ## Getting the code 10 | 11 | The project uses [sax-framework](https://github.com/tughi/sax-framework) as a submodule. 12 | 13 | ### Clone with submodules 14 | 15 | git clone --recurse-submodules https://github.com/tughi/aggregator-android.git 16 | 17 | ### Update existing clone 18 | 19 | git submodule update --init 20 | 21 | ## License 22 | 23 | Aggregator is licensed under GPLv3. See [LICENSE.md](./LICENSE.md) file for more details. 24 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /*/release 3 | 4 | /*.iml 5 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | 5 | android { 6 | namespace 'com.tughi.aggregator' 7 | compileSdk 34 8 | 9 | defaultConfig { 10 | applicationId 'com.tughi.aggregator' 11 | 12 | minSdkVersion 21 13 | targetSdkVersion 34 14 | 15 | versionName = 'Preview:034' 16 | versionCode = 299034 17 | } 18 | 19 | buildFeatures { 20 | buildConfig true 21 | } 22 | buildTypes { 23 | debug { 24 | applicationIdSuffix ".debug" 25 | 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | } 29 | release { 30 | minifyEnabled false 31 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 32 | } 33 | } 34 | 35 | flavorDimensions += 'store' 36 | 37 | productFlavors { 38 | create('fdroid') { 39 | dimension 'store' 40 | } 41 | create('google') { 42 | dimension 'store' 43 | applicationIdSuffix ".next" 44 | } 45 | } 46 | 47 | compileOptions { 48 | sourceCompatibility JavaVersion.VERSION_17 49 | targetCompatibility JavaVersion.VERSION_17 50 | } 51 | } 52 | 53 | dependencies { 54 | implementation 'androidx.appcompat:appcompat:1.7.0' 55 | implementation 'androidx.browser:browser:1.8.0' 56 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 57 | implementation 'androidx.core:core-ktx:1.13.1' 58 | implementation 'androidx.fragment:fragment-ktx:1.8.0' 59 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.2' 60 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.2' 61 | implementation 'androidx.preference:preference-ktx:1.2.1' 62 | implementation 'androidx.sqlite:sqlite-framework:2.4.0' 63 | implementation 'androidx.sqlite:sqlite-ktx:2.4.0' 64 | implementation 'androidx.viewpager2:viewpager2:1.1.0' 65 | implementation 'androidx.webkit:webkit:1.11.0' 66 | implementation 'com.amazon.ion:ion-element:1.0.0' 67 | implementation 'com.google.android.material:material:1.12.0' 68 | implementation 'com.squareup.okhttp3:okhttp:4.12.0' 69 | implementation 'commons-codec:commons-codec:1.13' 70 | 71 | implementation project(':sax-framework') 72 | } 73 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -dontobfuscate 2 | -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-hdpi/launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tughi/aggregator-android/9f082dbadb8f587b63ee98c753eef9c902926a1e/app/src/debug/res/mipmap-hdpi/launcher.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-mdpi/launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tughi/aggregator-android/9f082dbadb8f587b63ee98c753eef9c902926a1e/app/src/debug/res/mipmap-mdpi/launcher.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-xhdpi/launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tughi/aggregator-android/9f082dbadb8f587b63ee98c753eef9c902926a1e/app/src/debug/res/mipmap-xhdpi/launcher.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-xxhdpi/launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tughi/aggregator-android/9f082dbadb8f587b63ee98c753eef9c902926a1e/app/src/debug/res/mipmap-xxhdpi/launcher.png -------------------------------------------------------------------------------- /app/src/debug/res/mipmap-xxxhdpi/launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tughi/aggregator-android/9f082dbadb8f587b63ee98c753eef9c902926a1e/app/src/debug/res/mipmap-xxxhdpi/launcher.png -------------------------------------------------------------------------------- /app/src/debug/res/values/consts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | -------------------------------------------------------------------------------- /app/src/main/assets/database/020.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | DROP TRIGGER entry_fts__after_insert__entry_tag; 4 | 5 | -- 6 | 7 | CREATE TRIGGER entry_fts__after_insert__entry_tag AFTER INSERT ON entry_tag 8 | BEGIN 9 | UPDATE entry_fts SET tags = tags || ',' || NEW.tag_id WHERE docid = NEW.entry_id; 10 | END; 11 | 12 | -- 13 | 14 | DROP TRIGGER entry_fts__after_insert__feed_tag; 15 | 16 | -- 17 | 18 | CREATE TRIGGER entry_fts__after_insert__feed_tag AFTER INSERT ON feed_tag 19 | BEGIN 20 | UPDATE entry_fts SET tags = tags || ',' || NEW.tag_id WHERE docid IN (SELECT e.id FROM entry e WHERE e.feed_id = NEW.feed_id); 21 | END; 22 | 23 | -- 24 | 25 | PRAGMA user_version = 21; 26 | 27 | -- -------------------------------------------------------------------------------- /app/src/main/assets/database/021.sql: -------------------------------------------------------------------------------- 1 | -- 2 | 3 | ALTER TABLE feed ADD COLUMN cleanup_mode TEXT NOT NULL DEFAULT 'DEFAULT'; 4 | 5 | -- 6 | 7 | PRAGMA user_version = 22; 8 | 9 | -- 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/AppActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.lifecycle.Observer 6 | import com.tughi.aggregator.activities.main.MainActivity 7 | 8 | abstract class AppActivity : AppCompatActivity() { 9 | 10 | private var currentStyle: App.Style? = null 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | App.style.value?.let { style -> 14 | currentStyle = style 15 | 16 | setTheme(when { 17 | this is MainActivity -> style.theme.withoutActionBar 18 | else -> style.theme.default 19 | }) 20 | theme.apply { 21 | applyStyle(style.accent.default, true) 22 | applyStyle(style.navigationBar.default, true) 23 | } 24 | } 25 | 26 | super.onCreate(savedInstanceState) 27 | 28 | App.style.observe(this, Observer { style -> 29 | if (style != currentStyle) { 30 | recreate() 31 | } 32 | }) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator 2 | 3 | const val DATABASE_NAME = "data.sqlite" 4 | 5 | const val PREF_STYLE_THEME = "style__theme" 6 | const val PREF_STYLE_ACCENT = "style__accent" 7 | const val PREF_STYLE_NAVIGATION_BAR = "style__navigation_bar" 8 | 9 | const val JOB_SERVICE_FAVICONS_UPDATER = 1 10 | const val JOB_SERVICE_FEEDS_UPDATER = 2 11 | 12 | const val NOTIFICATION_CHANNEL__MY_FEED = "my_feed" 13 | const val NOTIFICATION__NEW_ENTRIES__MY_FEED = 1 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/Coroutines.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.Dispatchers 5 | 6 | val contentScope = CoroutineScope(Dispatchers.IO) 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/cleanupmode/CleanupModeViewHolders.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.cleanupmode 2 | 3 | import android.view.View 4 | import android.widget.TextView 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.tughi.aggregator.R 7 | import com.tughi.aggregator.data.CleanupMode 8 | 9 | internal sealed class CleanupModeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 10 | 11 | lateinit var cleanupMode: CleanupMode 12 | 13 | val titleTextView: TextView = itemView.findViewById(R.id.title) 14 | 15 | fun bind(cleanupMode: CleanupMode) { 16 | this.cleanupMode = cleanupMode 17 | 18 | titleTextView.text = cleanupMode.toString(titleTextView.context) 19 | 20 | onBind() 21 | } 22 | 23 | open fun onBind() {} 24 | 25 | } 26 | 27 | internal class UncheckedCleanupModeViewHolder(itemView: View) : CleanupModeViewHolder(itemView) 28 | 29 | internal class DefaultCleanupModeViewHolder(itemView: View) : CleanupModeViewHolder(itemView) 30 | 31 | internal class NeverCleanupModeViewHolder(itemView: View) : CleanupModeViewHolder(itemView) 32 | 33 | internal class Age3DaysCleanupModeViewHolder(itemView: View) : CleanupModeViewHolder(itemView) 34 | 35 | internal class Age1WeekCleanupModeViewHolder(itemView: View) : CleanupModeViewHolder(itemView) 36 | 37 | internal class Age1MonthCleanupModeViewHolder(itemView: View) : CleanupModeViewHolder(itemView) 38 | 39 | internal class Age3MonthsCleanupModeViewHolder(itemView: View) : CleanupModeViewHolder(itemView) 40 | 41 | internal class Age6MonthsCleanupModeViewHolder(itemView: View) : CleanupModeViewHolder(itemView) 42 | 43 | internal class Age1YearCleanupModeViewHolder(itemView: View) : CleanupModeViewHolder(itemView) 44 | 45 | internal class Age3YearsCleanupModeViewHolder(itemView: View) : CleanupModeViewHolder(itemView) 46 | 47 | internal class Age6YearsCleanupModeViewHolder(itemView: View) : CleanupModeViewHolder(itemView) 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/cleanupmode/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.cleanupmode 2 | 3 | import android.content.Context 4 | import com.tughi.aggregator.R 5 | import com.tughi.aggregator.data.Age1MonthCleanupMode 6 | import com.tughi.aggregator.data.Age1WeekCleanupMode 7 | import com.tughi.aggregator.data.Age1YearCleanupMode 8 | import com.tughi.aggregator.data.Age3DaysCleanupMode 9 | import com.tughi.aggregator.data.Age3MonthsCleanupMode 10 | import com.tughi.aggregator.data.Age3YearsCleanupMode 11 | import com.tughi.aggregator.data.Age6MonthsCleanupMode 12 | import com.tughi.aggregator.data.Age6YearsCleanupMode 13 | import com.tughi.aggregator.data.CleanupMode 14 | import com.tughi.aggregator.data.DefaultCleanupMode 15 | import com.tughi.aggregator.data.NeverCleanupMode 16 | import com.tughi.aggregator.preferences.UpdateSettings 17 | 18 | fun CleanupMode.toString(context: Context): String = when (this) { 19 | DefaultCleanupMode -> context.getString(R.string.cleanup_mode__default, UpdateSettings.defaultCleanupMode.toString(context)) 20 | Age3DaysCleanupMode -> context.getString(R.string.cleanup_mode__age__3_days) 21 | Age1WeekCleanupMode -> context.getString(R.string.cleanup_mode__age__1_week) 22 | Age1MonthCleanupMode -> context.getString(R.string.cleanup_mode__age__1_month) 23 | Age3MonthsCleanupMode -> context.getString(R.string.cleanup_mode__age__3_months) 24 | Age6MonthsCleanupMode -> context.getString(R.string.cleanup_mode__age__6_months) 25 | Age1YearCleanupMode -> context.getString(R.string.cleanup_mode__age__1_year) 26 | Age3YearsCleanupMode -> context.getString(R.string.cleanup_mode__age__3_years) 27 | Age6YearsCleanupMode -> context.getString(R.string.cleanup_mode__age__6_years) 28 | NeverCleanupMode -> context.getString(R.string.cleanup_mode__never) 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/entrytagrulesettings/DeleteEntryTagRuleDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.entrytagrulesettings 2 | 3 | import android.app.Dialog 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AlertDialog 6 | import androidx.fragment.app.DialogFragment 7 | import androidx.fragment.app.FragmentManager 8 | import com.tughi.aggregator.R 9 | import com.tughi.aggregator.data.DeleteEntryTagRuleCriteria 10 | import com.tughi.aggregator.data.EntryTagRules 11 | import com.tughi.aggregator.contentScope 12 | import kotlinx.coroutines.launch 13 | 14 | class DeleteEntryTagRuleDialogFragment : DialogFragment() { 15 | 16 | companion object { 17 | private const val ARG_ENTRY_TAG_RULE_ID = "entry_tag_rule_id" 18 | private const val ARG_FINISH_ACTIVITY = "finish_activity" 19 | 20 | fun show(fragmentManager: FragmentManager, entryTagRuleId: Long, finishActivity: Boolean) { 21 | DeleteEntryTagRuleDialogFragment() 22 | .apply { 23 | arguments = Bundle().apply { 24 | putLong(ARG_ENTRY_TAG_RULE_ID, entryTagRuleId) 25 | putBoolean(ARG_FINISH_ACTIVITY, finishActivity) 26 | } 27 | } 28 | .show(fragmentManager, "delete-dialog") 29 | } 30 | } 31 | 32 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 33 | val arguments = requireArguments() 34 | val entryTagRuleId = arguments.getLong(ARG_ENTRY_TAG_RULE_ID) 35 | val finishActivity = arguments.getBoolean(ARG_FINISH_ACTIVITY) 36 | return AlertDialog.Builder(requireContext()) 37 | .setMessage(R.string.entry_tag_rule__delete__message) 38 | .setNegativeButton(R.string.action__no, null) 39 | .setPositiveButton(R.string.action__yes) { _, _ -> 40 | contentScope.launch { 41 | EntryTagRules.delete(DeleteEntryTagRuleCriteria(entryTagRuleId)) 42 | } 43 | if (finishActivity) { 44 | activity?.finish() 45 | } 46 | } 47 | .create() 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/feedsettings/FeedSettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.feedsettings 2 | 3 | import android.os.Bundle 4 | import com.tughi.aggregator.AppActivity 5 | 6 | class FeedSettingsActivity : AppActivity() { 7 | 8 | companion object { 9 | const val EXTRA_FEED_ID = "feed_id" 10 | } 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | 15 | val fragmentManager = supportFragmentManager 16 | var fragment = fragmentManager.findFragmentById(android.R.id.content) 17 | if (fragment == null) { 18 | val intentExtras = intent.extras!! 19 | fragment = FeedSettingsFragment().apply { 20 | arguments = Bundle().apply { 21 | putLong(FeedSettingsFragment.ARG_FEED_ID, intentExtras.getLong(EXTRA_FEED_ID)) 22 | } 23 | } 24 | fragmentManager.beginTransaction() 25 | .replace(android.R.id.content, fragment) 26 | .commit() 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/feedsettings/UnsubscribeDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.feedsettings 2 | 3 | import android.app.Dialog 4 | import android.os.Bundle 5 | import androidx.appcompat.app.AlertDialog 6 | import androidx.fragment.app.DialogFragment 7 | import androidx.fragment.app.FragmentManager 8 | import com.tughi.aggregator.R 9 | import com.tughi.aggregator.data.Feeds 10 | import com.tughi.aggregator.contentScope 11 | import com.tughi.aggregator.services.AutoUpdateScheduler 12 | import kotlinx.coroutines.launch 13 | 14 | class UnsubscribeDialogFragment : DialogFragment() { 15 | 16 | companion object { 17 | const val ARG_FEED_ID = "feed_id" 18 | const val ARG_FEED_TITLE = "feed_title" 19 | const val ARG_FINISH_ACTIVITY = "finish_activity" 20 | 21 | fun show(fragmentManager: FragmentManager, feedId: Long, feedTitle: String, finishActivity: Boolean) { 22 | UnsubscribeDialogFragment() 23 | .apply { 24 | arguments = Bundle().apply { 25 | putLong(ARG_FEED_ID, feedId) 26 | putString(ARG_FEED_TITLE, feedTitle) 27 | putBoolean(ARG_FINISH_ACTIVITY, finishActivity) 28 | } 29 | } 30 | .show(fragmentManager, "unsubscribe-dialog") 31 | } 32 | } 33 | 34 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 35 | val arguments = requireArguments() 36 | val feedId = arguments.getLong(ARG_FEED_ID) 37 | val feedTitle = arguments.getString(ARG_FEED_TITLE) 38 | val finishActivity = arguments.getBoolean(ARG_FINISH_ACTIVITY) 39 | return AlertDialog.Builder(requireContext()) 40 | .setTitle(feedTitle) 41 | .setMessage(R.string.unsubscribe_feed__message) 42 | .setNegativeButton(R.string.action__no, null) 43 | .setPositiveButton(R.string.action__yes) { _, _ -> 44 | contentScope.launch { 45 | Feeds.delete(Feeds.DeleteFeedCriteria(feedId)) 46 | 47 | AutoUpdateScheduler.schedule() 48 | } 49 | if (finishActivity) { 50 | activity?.finish() 51 | } 52 | } 53 | .create() 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/main/EntriesFragmentAdapterListener.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.main 2 | 3 | internal interface EntriesFragmentAdapterListener { 4 | 5 | fun onEntryClicked(entry: EntriesFragmentViewModel.Entry, position: Int) 6 | 7 | fun onEntrySelectorClicked(entry: EntriesFragmentViewModel.Entry, position: Int) 8 | 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/main/FeedEntriesFragment.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.main 2 | 3 | import android.os.Bundle 4 | import androidx.lifecycle.Observer 5 | import androidx.lifecycle.ViewModelProvider 6 | import com.tughi.aggregator.data.EntriesQueryCriteria 7 | import com.tughi.aggregator.data.FeedEntriesQueryCriteria 8 | import com.tughi.aggregator.preferences.EntryListSettings 9 | 10 | class FeedEntriesFragment : EntriesFragment() { 11 | 12 | private val feedId by lazy { requireArguments().getLong(ARGUMENT_FEED_ID) } 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | 17 | val viewModelFactory = FeedEntriesFragmentViewModel.Factory(feedId) 18 | val viewModel = ViewModelProvider(this, viewModelFactory).get(FeedEntriesFragmentViewModel::class.java) 19 | viewModel.feed.observe(this, Observer { feed -> 20 | if (feed != null) { 21 | setTitle(feed.title) 22 | } 23 | }) 24 | } 25 | 26 | override val initialQueryCriteria: EntriesQueryCriteria 27 | get() = FeedEntriesQueryCriteria(feedId = feedId, sessionTime = System.currentTimeMillis(), showRead = EntryListSettings.showReadEntries, sortOrder = EntryListSettings.entriesSortOrder) 28 | 29 | override fun onNavigationClick() { 30 | parentFragmentManager.popBackStack() 31 | } 32 | 33 | companion object { 34 | const val ARGUMENT_FEED_ID = "feed_id" 35 | 36 | fun newInstance(feedId: Long): FeedEntriesFragment { 37 | return FeedEntriesFragment().also { 38 | it.arguments = Bundle().apply { 39 | putLong(ARGUMENT_FEED_ID, feedId) 40 | } 41 | } 42 | } 43 | } 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/main/FeedEntriesFragmentViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.main 2 | 3 | import android.database.Cursor 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.ViewModelProvider 7 | import com.tughi.aggregator.data.Feeds 8 | 9 | class FeedEntriesFragmentViewModel(feedId: Long) : ViewModel() { 10 | 11 | val feed: LiveData = Feeds.liveQueryOne(Feeds.QueryRowCriteria(feedId), Feed.QueryHelper) 12 | 13 | class Feed(val title: String) { 14 | object QueryHelper : Feeds.QueryHelper( 15 | Feeds.TITLE, 16 | Feeds.CUSTOM_TITLE 17 | ) { 18 | override fun createRow(cursor: Cursor) = Feed( 19 | title = cursor.getString(1) ?: cursor.getString(0) 20 | ) 21 | } 22 | } 23 | 24 | class Factory(private val feedId: Long) : ViewModelProvider.Factory { 25 | 26 | override fun create(modelClass: Class): T { 27 | if (modelClass.isAssignableFrom(FeedEntriesFragmentViewModel::class.java)) { 28 | @Suppress("UNCHECKED_CAST") 29 | return FeedEntriesFragmentViewModel(feedId) as T 30 | } 31 | throw UnsupportedOperationException() 32 | } 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/main/FeedsFragmentFeedAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.main 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.ListAdapter 7 | import com.tughi.aggregator.R 8 | 9 | internal class FeedsFragmentFeedAdapter(private val listener: FeedsFragmentFeedAdapterListener) : ListAdapter(DiffCallback()) { 10 | 11 | override fun getItemViewType(position: Int): Int = when (getItem(position).expanded) { 12 | true -> R.layout.feeds_item_expanded 13 | false -> R.layout.feeds_item_collapsed 14 | } 15 | 16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedsFragmentFeedViewHolder { 17 | val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) 18 | val viewHolder = when (viewType) { 19 | R.layout.feeds_item_expanded -> FeedsFragmentExpandedFeedViewHolder(view) 20 | else -> FeedsFragmentCollapsedFeedViewHolder(view) 21 | } 22 | 23 | viewHolder.itemView.setOnClickListener { 24 | listener.onFeedClicked(viewHolder.feed) 25 | } 26 | viewHolder.toggle.setOnClickListener { 27 | listener.onToggleFeed(viewHolder.feed) 28 | } 29 | 30 | if (viewHolder is FeedsFragmentExpandedFeedViewHolder) { 31 | viewHolder.updateButton.setOnClickListener { 32 | listener.onUpdateFeed(viewHolder.feed) 33 | } 34 | } 35 | 36 | return viewHolder 37 | } 38 | 39 | override fun onBindViewHolder(holder: FeedsFragmentFeedViewHolder, position: Int) { 40 | holder.onBind(getItem(position)) 41 | } 42 | 43 | private class DiffCallback : DiffUtil.ItemCallback() { 44 | 45 | override fun areItemsTheSame(oldFeed: FeedsFragmentViewModel.Feed, newFeed: FeedsFragmentViewModel.Feed): Boolean { 46 | return oldFeed.id == newFeed.id 47 | } 48 | 49 | override fun areContentsTheSame(oldFeed: FeedsFragmentViewModel.Feed, newFeed: FeedsFragmentViewModel.Feed): Boolean { 50 | return oldFeed == newFeed 51 | } 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/main/FeedsFragmentFeedAdapterListener.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.main 2 | 3 | internal interface FeedsFragmentFeedAdapterListener { 4 | 5 | fun onFeedClicked(feed: FeedsFragmentViewModel.Feed) 6 | 7 | fun onToggleFeed(feed: FeedsFragmentViewModel.Feed) 8 | 9 | fun onUpdateFeed(feed: FeedsFragmentViewModel.Feed) 10 | 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/main/MyFeedFragment.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.main 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.MenuItem 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.appcompat.widget.Toolbar 9 | import com.tughi.aggregator.R 10 | import com.tughi.aggregator.activities.myfeedsettings.MyFeedSettingsActivity 11 | import com.tughi.aggregator.data.EntriesQueryCriteria 12 | import com.tughi.aggregator.data.MyFeedEntriesQueryCriteria 13 | import com.tughi.aggregator.preferences.EntryListSettings 14 | 15 | class MyFeedFragment : EntriesFragment() { 16 | 17 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 18 | val fragmentView = super.onCreateView(inflater, container, savedInstanceState) ?: return null 19 | 20 | setTitle(R.string.title_my_feed) 21 | 22 | fragmentView.findViewById(R.id.toolbar) 23 | .inflateMenu(R.menu.my_feed_fragment) 24 | 25 | return fragmentView 26 | } 27 | 28 | override val initialQueryCriteria: EntriesQueryCriteria 29 | get() = MyFeedEntriesQueryCriteria(sessionTime = System.currentTimeMillis(), showRead = EntryListSettings.showReadEntries, sortOrder = EntryListSettings.entriesSortOrder) 30 | 31 | override fun onMenuItemClick(item: MenuItem?): Boolean { 32 | when (item?.itemId) { 33 | R.id.settings -> { 34 | context?.let { MyFeedSettingsActivity.start(it) } 35 | } 36 | else -> { 37 | return super.onMenuItemClick(item) 38 | } 39 | } 40 | return true 41 | } 42 | 43 | override fun onNavigationClick() { 44 | val activity = activity as MainActivity 45 | activity.openDrawer() 46 | } 47 | 48 | companion object { 49 | fun newInstance(): MyFeedFragment { 50 | return MyFeedFragment() 51 | } 52 | } 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/myfeedsettings/MyFeedSettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.myfeedsettings 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import com.tughi.aggregator.AppActivity 7 | 8 | class MyFeedSettingsActivity : AppActivity() { 9 | 10 | companion object { 11 | fun start(context: Context) { 12 | context.startActivity(Intent(context, MyFeedSettingsActivity::class.java)) 13 | } 14 | } 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | 19 | val fragmentManager = supportFragmentManager 20 | var fragment = fragmentManager.findFragmentById(android.R.id.content) 21 | if (fragment == null) { 22 | fragment = MyFeedSettingsFragment() 23 | fragmentManager.beginTransaction() 24 | .replace(android.R.id.content, fragment) 25 | .commit() 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/notifications/NewEntriesActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.notifications 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import com.tughi.aggregator.AppActivity 6 | import com.tughi.aggregator.activities.main.MainActivity 7 | 8 | class NewEntriesActivity : AppActivity() { 9 | 10 | override fun onCreate(savedInstanceState: Bundle?) { 11 | super.onCreate(savedInstanceState) 12 | 13 | val intent = Intent(this, MainActivity::class.java).apply { 14 | action = MainActivity.ACTION_VIEW_MY_FEED 15 | flags = Intent.FLAG_ACTIVITY_CLEAR_TOP 16 | } 17 | startActivity(intent) 18 | 19 | finish() 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/tughi/aggregator/activities/opml/OpmlExportActivity.kt: -------------------------------------------------------------------------------- 1 | package com.tughi.aggregator.activities.opml 2 | 3 | import android.os.Bundle 4 | import android.widget.Button 5 | import androidx.activity.result.contract.ActivityResultContracts 6 | import androidx.lifecycle.ViewModelProvider 7 | import androidx.lifecycle.get 8 | import com.tughi.aggregator.AppActivity 9 | import com.tughi.aggregator.R 10 | import com.tughi.aggregator.contentScope 11 | import com.tughi.aggregator.data.Feeds 12 | import com.tughi.aggregator.feeds.OpmlFeed 13 | import com.tughi.aggregator.feeds.OpmlGenerator 14 | import com.tughi.aggregator.utilities.has 15 | import kotlinx.coroutines.launch 16 | 17 | class OpmlExportActivity : AppActivity() { 18 | private lateinit var viewModel: OpmlFeedsViewModel 19 | 20 | private val requestDocument = registerForActivityResult(ActivityResultContracts.CreateDocument("text/xml")) { uri -> 21 | if (uri != null) { 22 | viewModel.feeds.value?.let { feeds -> 23 | contentResolver.openOutputStream(uri)?.use { outputStream -> 24 | contentScope.launch { 25 | OpmlGenerator.generate(feeds, outputStream) 26 | } 27 | 28 | finish() 29 | } 30 | } 31 | } 32 | } 33 | 34 | override fun onCreate(savedInstanceState: Bundle?) { 35 | super.onCreate(savedInstanceState) 36 | 37 | setContentView(R.layout.opml_export_activity) 38 | 39 | supportActionBar?.apply { 40 | setDisplayHomeAsUpEnabled(true) 41 | setHomeAsUpIndicator(R.drawable.action_back) 42 | } 43 | 44 | viewModel = ViewModelProvider(this).get() 45 | 46 | val exportButton = findViewById