├── .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 | [](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