├── .github └── workflows │ └── main.yml ├── .gitignore ├── CONTRIBUTORS ├── LICENSE ├── Readme.md ├── app ├── .gitignore ├── build.gradle ├── lint.xml ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── about │ │ │ └── about.html │ ├── ic_launcher-web.png │ ├── java │ │ └── me │ │ │ └── murks │ │ │ └── feedwatcher │ │ │ ├── AndroidApplication.kt │ │ │ ├── AndroidEnvironment.kt │ │ │ ├── AndroidSettings.kt │ │ │ ├── Constants.kt │ │ │ ├── Either.kt │ │ │ ├── Environment.kt │ │ │ ├── FeedItems.kt │ │ │ ├── FeedWatcherApp.kt │ │ │ ├── FeedwatcherLog.kt │ │ │ ├── HtmlTags.kt │ │ │ ├── Jobs.kt │ │ │ ├── Lookup.kt │ │ │ ├── Notifications.kt │ │ │ ├── ResourceTracker.kt │ │ │ ├── Settings.kt │ │ │ ├── Texts.kt │ │ │ ├── activities │ │ │ ├── AboutActivity.kt │ │ │ ├── Activities.kt │ │ │ ├── DateTimePickerDialogFragment.kt │ │ │ ├── Dialogs.kt │ │ │ ├── FeedActivity.kt │ │ │ ├── FeedExportActivity.kt │ │ │ ├── FeedExportRecyclerViewAdapter.kt │ │ │ ├── FeedImportActivity.kt │ │ │ ├── FeedImportFragment.kt │ │ │ ├── FeedImportRecyclerViewAdapter.kt │ │ │ ├── FeedUiContainer.kt │ │ │ ├── FeedWatcherAsyncLoadingFragment.kt │ │ │ ├── FeedWatcherBaseActivity.kt │ │ │ ├── FeedWatcherBaseFragment.kt │ │ │ ├── FeedsFragment.kt │ │ │ ├── FeedsRecyclerViewAdapter.kt │ │ │ ├── FilterRecyclerViewAdapter.kt │ │ │ ├── FilterUiModel.kt │ │ │ ├── Formatter.kt │ │ │ ├── ListRecyclerViewAdapter.kt │ │ │ ├── OverviewActivity.kt │ │ │ ├── PreferencesActivity.kt │ │ │ ├── PreferencesFragment.kt │ │ │ ├── QueriesFragment.kt │ │ │ ├── QueryActivity.kt │ │ │ ├── QueryRecyclerViewAdapter.kt │ │ │ ├── ResultActivity.kt │ │ │ ├── ResultsFragment.kt │ │ │ ├── ResultsRecyclerViewAdapter.kt │ │ │ └── SelectableRecyclerViewAdapter.kt │ │ │ ├── data │ │ │ ├── AddFeeds.kt │ │ │ ├── ClearResults.kt │ │ │ ├── Cursors.kt │ │ │ ├── DataStore.kt │ │ │ ├── DeleteFeed.kt │ │ │ ├── DeleteResult.kt │ │ │ ├── FeedWatcherSchema.java │ │ │ ├── RecordScan.kt │ │ │ ├── UnitOfWork.kt │ │ │ └── UpdateResult.kt │ │ │ ├── model │ │ │ ├── ContainsFilter.kt │ │ │ ├── Feed.kt │ │ │ ├── FeedFilter.kt │ │ │ ├── Filter.kt │ │ │ ├── FilterFactory.kt │ │ │ ├── FilterParameter.kt │ │ │ ├── FilterType.kt │ │ │ ├── FilterTypeCallback.kt │ │ │ ├── NewEntryFilter.kt │ │ │ ├── Query.kt │ │ │ ├── Result.kt │ │ │ ├── Scan.kt │ │ │ └── ScanInterval.kt │ │ │ └── tasks │ │ │ ├── ActionTask.kt │ │ │ ├── FeedsFilter.kt │ │ │ ├── FilterFeedsJob.kt │ │ │ ├── StreamingTask.kt │ │ │ ├── TaskListener.kt │ │ │ └── Tasks.kt │ └── res │ │ ├── drawable │ │ ├── ic_feed_icon.xml │ │ ├── ic_feedwatcher_notification.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_launcher_foreground.xml │ │ └── ic_menu.xml │ │ ├── layout │ │ ├── activity_about.xml │ │ ├── activity_feed.xml │ │ ├── activity_feed_export.xml │ │ ├── activity_feed_export_list_item.xml │ │ ├── activity_feed_import.xml │ │ ├── activity_feed_import_list_item.xml │ │ ├── activity_overview.xml │ │ ├── activity_query.xml │ │ ├── activity_result.xml │ │ ├── fragment_feed_import.xml │ │ ├── fragment_feeds_list.xml │ │ ├── fragment_feeds_list_item.xml │ │ ├── fragment_queries_list.xml │ │ ├── fragment_queries_list_item.xml │ │ ├── fragment_results_list.xml │ │ ├── fragment_results_list_item.xml │ │ ├── layout_date_time_picker.xml │ │ ├── preferences_activity.xml │ │ └── query_filter_list_item.xml │ │ ├── menu │ │ ├── activity_query_menu.xml │ │ ├── fragment_feeds_menu.xml │ │ ├── fragment_results_menu.xml │ │ └── navigation.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── values-zh │ │ └── strings.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── preferences.xml │ └── test │ └── java │ └── me │ └── murks │ └── feedwatcher │ └── TextsTests.kt ├── atomrss-cli ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── me │ └── murks │ └── feedwatcher │ └── atomrss │ └── cli │ ├── AtomRssFileVisitor.java │ └── FeedwatcherAtomRssCli.java ├── atomrss ├── .gitignore ├── build.gradle └── src │ ├── main │ └── java │ │ └── me │ │ └── murks │ │ └── feedwatcher │ │ └── atomrss │ │ ├── FeedItem.kt │ │ ├── FeedParser.kt │ │ ├── LazyParser.kt │ │ └── ParserNode.kt │ └── test │ └── java │ └── me │ └── murks │ └── feedwatcher │ └── atomrss │ └── FeedParserTests.kt ├── build.gradle ├── fastlane └── metadata │ └── android │ └── en │ ├── full_description.txt │ ├── name.txt │ └── short_description.txt ├── feed-icon.svg ├── feedwatcher-icon.png ├── feedwatcher-icon.svg ├── feedwatcher-notification-path.svg ├── feedwatcher-notification.svg ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Gradle build 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: [push, pull_request] 8 | 9 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 10 | jobs: 11 | # This workflow contains a single job called "build" 12 | build: 13 | # The type of runner that the job will run on 14 | runs-on: ubuntu-latest 15 | 16 | # Steps represent a sequence of tasks that will be executed as part of the job 17 | steps: 18 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 19 | - uses: actions/checkout@v2 20 | 21 | # Runs a single command using the runners shell 22 | - name: Run a one-line script 23 | run: ./gradlew build 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/** 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .idea/caches/build_file_checksums.ser 10 | .idea/assetWizardSettings.xml 11 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | zouroboros 2 | sr093906 3 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | FeedWatcher 2 | =========== 3 | 4 | [Get it on F-Droid](https://f-droid.org/packages/me.murks.feedwatcher) 7 | 8 | Official repository for the FeedWatcher app. FeedWatcher allows you to monitor 9 | RSS/ATOM feeds with customizable queries. 10 | 11 | FeedWatcher is currently under development towards an initial release and 12 | still contains a lot of bugs. If you find any bugs you are welcome to create a 13 | new issue. 14 | 15 | FeedWatcher is licensed under the GNU General Public License v3.0 (GNU GPLv3) or any later version. You can find the license text in the LICENSE file. 16 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'org.jetbrains.dokka' 6 | 7 | apply plugin: 'kotlin-kapt' 8 | 9 | apply plugin: "de.mannodermaus.android-junit5" 10 | 11 | android { 12 | compileSdkVersion 31 13 | defaultConfig { 14 | applicationId "me.murks.feedwatcher" 15 | minSdkVersion 24 16 | targetSdkVersion 31 17 | versionCode 27 18 | versionName "0.0.27-alpha" 19 | buildFeatures { 20 | dataBinding true 21 | viewBinding true 22 | } 23 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 24 | } 25 | 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | 33 | 34 | // To inline the bytecode built with JVM target 1.8 into 35 | // bytecode that is being built with JVM target 1.6. (e.g. navArgs) 36 | compileOptions { 37 | sourceCompatibility JavaVersion.VERSION_1_8 38 | targetCompatibility JavaVersion.VERSION_1_8 39 | } 40 | 41 | kotlinOptions { 42 | jvmTarget = "1.8" 43 | } 44 | 45 | lint { 46 | disable 'MissingTranslation' 47 | showAll true 48 | textOutput file('stdout') 49 | textReport true 50 | } 51 | } 52 | 53 | dokka { 54 | outputFormat = 'html' 55 | outputDirectory = "$buildDir/javadoc" 56 | } 57 | 58 | dependencies { 59 | implementation fileTree(dir: 'libs', include: ['*.jar']) 60 | implementation project(':atomrss') 61 | 62 | implementation 'com.github.zouroboros:sql-schema-spec:master-SNAPSHOT' 63 | implementation 'com.github.zouroboros:jopl:0.0.4-alpha' 64 | 65 | implementation 'org.jsoup:jsoup:1.11.3' 66 | 67 | // okhttp dependencies 68 | implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.1")) 69 | // define any required OkHttp artifacts without version 70 | implementation("com.squareup.okhttp3:okhttp") 71 | implementation("com.squareup.okhttp3:logging-interceptor") 72 | 73 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 74 | implementation 'androidx.appcompat:appcompat:1.3.1' 75 | implementation 'androidx.constraintlayout:constraintlayout:2.1.0' 76 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 77 | implementation 'androidx.recyclerview:recyclerview:1.2.1' 78 | implementation 'androidx.preference:preference-ktx:1.1.1' 79 | implementation 'com.google.android.material:material:1.5.0-alpha01' 80 | 81 | implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' 82 | implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' 83 | 84 | // (Required) Writing and executing Unit Tests on the JUnit Platform 85 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.0") 86 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.0") 87 | testImplementation 'net.sf.kxml:kxml2:2.3.0' 88 | 89 | androidTestImplementation 'androidx.test:runner:1.4.0' 90 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 91 | } 92 | -------------------------------------------------------------------------------- /app/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zouroboros/feed-watcher/9e8f0b3ac07c5f95ec407a390283537a3029076a/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/AndroidApplication.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | import android.app.Application 21 | 22 | /** 23 | * @author zouroboros 24 | */ 25 | @Suppress 26 | class AndroidApplication(): Application() { 27 | override fun onCreate() { 28 | super.onCreate() 29 | 30 | Notifications(this).createNotificationChannel() 31 | 32 | val app = FeedWatcherApp(AndroidEnvironment(this)) 33 | app.rescheduleJobs() 34 | app.environment.close() 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/AndroidEnvironment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019-2020 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | import android.content.Context 21 | import me.murks.feedwatcher.data.DataStore 22 | 23 | /** 24 | * @author zouroboros 25 | */ 26 | class AndroidEnvironment(context: Context): Environment { 27 | override val dataStore = DataStore(context) 28 | override val settings = AndroidSettings(context) 29 | override val jobs = Jobs(context) 30 | override val notifications = Notifications(context) 31 | override val log = FeedwatcherLog() 32 | override fun close() { 33 | dataStore.close() 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/AndroidSettings.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | import android.content.Context 21 | import androidx.preference.PreferenceManager 22 | 23 | class AndroidSettings(val context: Context): Settings { 24 | private val preferences = PreferenceManager.getDefaultSharedPreferences(context) 25 | 26 | init { 27 | // perform migrations 28 | if (!preferences.contains(Constants.scanIntervalTableIdPreferencesKey)) { 29 | val edit = preferences.edit() 30 | edit.putInt(Constants.scanIntervalTableIdPreferencesKey, 0) 31 | edit.putInt(Constants.scanIntervalIdPreferencesKey, 32 | preferences.getInt(Constants.scanIntervalPreferencesKey, 3) - 1) 33 | edit.remove(Constants.scanIntervalPreferencesKey) 34 | edit.commit() 35 | } 36 | 37 | // update scan interval table id 38 | if (preferences.getInt(Constants.scanIntervalTableIdPreferencesKey, -1) != 1) { 39 | val edit = preferences.edit() 40 | edit.putInt(Constants.scanIntervalTableIdPreferencesKey, 1) 41 | edit.putInt(Constants.scanIntervalIdPreferencesKey, 42 | preferences.getInt(Constants.scanIntervalIdPreferencesKey, 0) + 2) 43 | edit.commit() 44 | } 45 | } 46 | 47 | override val showNotifications: Boolean 48 | get() = preferences.getBoolean(Constants.notificationsPreferencesKey, true) 49 | override val backgroundScanning: Boolean 50 | get() = preferences.getBoolean(Constants.backgroundScanningPreferencesKey, true) 51 | override val scanIntervalTableId: Int 52 | get() = preferences.getInt(Constants.scanIntervalTableIdPreferencesKey, 0) 53 | override val scanIntervalId: Int 54 | get() = preferences.getInt(Constants.scanIntervalIdPreferencesKey, 2) 55 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/Constants.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | /** 21 | * Object for holding constants that are used in xml resources as well as code 22 | * @author zouroboros 23 | */ 24 | object Constants { 25 | val scanIntervalPreferencesKey = "scan_interval" 26 | val backgroundScanningPreferencesKey = "scan_background_scan" 27 | val notificationsPreferencesKey = "new_results_notification" 28 | val scanIntervalTableIdPreferencesKey = "scan_interval_table_id" 29 | val scanIntervalIdPreferencesKey = "scan_interval_id" 30 | val scanIntervalInfo = "scan_interval_info" 31 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/Either.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2020 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | /** 21 | * Classes representing a success value (Right) or a error value (Left) 22 | * @author zouroboros 23 | */ 24 | sealed class Either(protected val leftValue: TL?, protected val rightValue: TR?) { 25 | fun isLeft() = leftValue != null 26 | fun isRight() = rightValue != null 27 | 28 | fun either(leftMapper: (TL) -> TResult, rightMapper: (TR) -> TResult) 29 | = if (isLeft()) leftMapper(leftValue!!) else rightMapper(rightValue!!) 30 | } 31 | 32 | data class Left(val value: TL) : Either(value, null) 33 | 34 | data class Right(val value: TR) : Either(null, value) -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/Environment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019-2020 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | import me.murks.feedwatcher.data.DataStore 21 | 22 | /** 23 | * @author zouroboros 24 | */ 25 | interface Environment: AutoCloseable { 26 | val dataStore: DataStore 27 | val settings: Settings 28 | val jobs: Jobs 29 | val notifications: Notifications 30 | val log: FeedwatcherLog 31 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/FeedItems.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | import android.content.Context 21 | import me.murks.feedwatcher.atomrss.FeedItem 22 | 23 | fun FeedItem.itemTitle(context: Context) = 24 | this.title ?: this.description ?: context.getString(R.string.feed_item_no_tile) 25 | 26 | fun FeedItem.itemDescription(context: Context) = 27 | this.description ?: this.title ?: context.getString(R.string.feed_item_no_description) -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/FeedwatcherLog.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2020 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | import android.util.Log 21 | 22 | /** 23 | * Wrapper for the android log functions that automatically generates tag based on the calling class and method. 24 | * @author zouroboros 25 | */ 26 | class FeedwatcherLog { 27 | fun info(message: String) { 28 | Log.v(tag(), message) 29 | } 30 | 31 | fun error(message: String) { 32 | Log.e(tag(), message) 33 | } 34 | 35 | fun error(message: String, exception: Throwable) { 36 | Log.e(tag(), message, exception) 37 | } 38 | 39 | private fun tag(): String { 40 | val caller = Thread.currentThread().stackTrace.first { it.className.startsWith("me.murks.feedwatcher") && it.className != javaClass.name } 41 | return "${caller.className}.${caller.methodName}" 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/HtmlTags.kt: -------------------------------------------------------------------------------- 1 | package me.murks.feedwatcher 2 | 3 | import org.jsoup.Jsoup 4 | 5 | /** 6 | * @author zouroboros 7 | */ 8 | object HtmlTags { 9 | fun text(html: String): String { 10 | return Jsoup.parseBodyFragment(html).text() 11 | } 12 | 13 | fun wrapInDocument(html: String, backgroundColor: Int): String { 14 | val document = Jsoup.parse(html) 15 | val color = cssColor(backgroundColor) 16 | document.body().attr("style", 17 | "padding: 0; margin: 0; background-color: $color;") 18 | return document.html() 19 | } 20 | 21 | private fun cssColor(color: Int): String { 22 | val A = color shr 24 and 0xff // or color >>> 24 23 | val R = color shr 16 and 0xff 24 | val G = color shr 8 and 0xff 25 | val B = color and 0xff 26 | return "rgba($R, $G, $B, $A);" 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/Jobs.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | import android.app.job.JobInfo 21 | import android.app.job.JobScheduler 22 | import android.content.ComponentName 23 | import android.content.Context 24 | import me.murks.feedwatcher.model.ScanInterval 25 | import me.murks.feedwatcher.tasks.FilterFeedsJob 26 | import java.lang.RuntimeException 27 | import kotlin.math.max 28 | 29 | /** 30 | * Class for managing background jobs 31 | * @author zouroboros 32 | */ 33 | class Jobs(private val context: Context) { 34 | /** 35 | * minimum intervals for background scanning in milliseconds 36 | */ 37 | val minimumInterval = JobInfo.getMinPeriodMillis() 38 | 39 | /** 40 | * accuracy for scanning intervals in milliseconds 41 | */ 42 | val intervalAccuracy = JobInfo.getMinFlexMillis() 43 | 44 | fun rescheduleJobs(scanInterval: ScanInterval) { 45 | val jobScheduler = context.getSystemService(JobScheduler::class.java) 46 | 47 | jobScheduler.cancelAll() 48 | 49 | if(jobScheduler.allPendingJobs.isEmpty()) { 50 | val jobBuilder = JobInfo.Builder(1, ComponentName(context, FilterFeedsJob::class.java)) 51 | 52 | val interval = scanInterval.hours * 60 * 60 * 1000L + scanInterval.minutes * 60 * 1000L 53 | 54 | jobBuilder.setPeriodic(interval, max(JobInfo.getMinFlexMillis(), interval/10)) 55 | .setPersisted(true) 56 | .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 57 | val result = jobScheduler.schedule(jobBuilder.build()) 58 | if(result != JobScheduler.RESULT_SUCCESS) { 59 | throw RuntimeException("Couldn't schedule job!") 60 | } 61 | // TODO only schedule job when at least one query is set up 62 | } 63 | } 64 | 65 | fun removeSchedule() { 66 | val jobScheduler = context.getSystemService(JobScheduler::class.java) 67 | jobScheduler.cancelAll() 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/Lookup.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | import java.util.* 21 | import kotlin.collections.HashMap 22 | 23 | /** 24 | * Associates a key with a collection of values. 25 | * 26 | * @author zouroboros 27 | */ 28 | class Lookup(private val map: MutableMap>): Iterable>> { 29 | fun append(key: K, value: V) { 30 | if(!map.containsKey(key)) { 31 | map[key] = LinkedList() 32 | } 33 | map[key]!!.add(value) 34 | } 35 | 36 | fun values(key: K) = map.get(key) 37 | 38 | fun flatList(): Collection> = map.entries.map { 39 | it.value.map { value -> AbstractMap.SimpleEntry(it.key, value) } }.flatten() 40 | 41 | override fun iterator(): Iterator>> = map.asIterable().iterator() 42 | 43 | fun merge(otherLookup: Lookup): Lookup { 44 | otherLookup.forEach { 45 | if (map.containsKey(it.key)) { 46 | map[it.key]!!.addAll(it.value) 47 | } else { 48 | map[it.key] = LinkedList(it.value) 49 | } 50 | } 51 | 52 | return this 53 | } 54 | } 55 | 56 | fun Map>.toLookup(): Lookup { 57 | return Lookup(HashMap(this.mapValues { LinkedList(it.value) })) 58 | } 59 | 60 | fun Collection.toLookup(key: (T) -> K, value: (T) -> V): Lookup { 61 | return this.groupBy(key, value).toLookup() 62 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/Notifications.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | import android.app.NotificationChannel 21 | import android.app.NotificationManager 22 | import android.app.PendingIntent 23 | import android.content.Context 24 | import android.content.Intent 25 | import android.os.Build 26 | import androidx.core.app.NotificationCompat 27 | import androidx.core.app.NotificationManagerCompat 28 | import me.murks.feedwatcher.activities.OverviewActivity 29 | import me.murks.feedwatcher.tasks.FilterFeedsJob 30 | import me.murks.feedwatcher.model.Result 31 | 32 | /** 33 | * Class for managing notifications 34 | * @author zouroboros 35 | */ 36 | class Notifications(private val context: Context) { 37 | 38 | companion object { 39 | const val CHANNEL_ID = "result.notification.channel" 40 | } 41 | 42 | fun createNotificationChannel() { 43 | // Create the NotificationChannel, but only on API 26+ because 44 | // the NotificationChannel class is new and not in the support library 45 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 46 | val name = context.getString(R.string.result_notification_channel_name) 47 | val description = context.getString(R.string.result_notification_channel_description) 48 | val importance = NotificationManager.IMPORTANCE_DEFAULT 49 | val channel = NotificationChannel(CHANNEL_ID, name, importance) 50 | channel.description = description 51 | channel.importance = NotificationManager.IMPORTANCE_LOW 52 | val notificationManager = context.getSystemService(NotificationManager::class.java) 53 | notificationManager!!.createNotificationChannel(channel) 54 | } 55 | } 56 | 57 | fun newResults(results: List, settings: Settings) { 58 | val notificationBuilder = NotificationCompat.Builder(context, CHANNEL_ID) 59 | notificationBuilder.setSmallIcon(R.drawable.ic_feedwatcher_notification) 60 | notificationBuilder.setContentTitle(context.getString(R.string.result_notification_title)) 61 | 62 | val feeds = results.map { it.feed.name }.distinct().joinToString(", ") 63 | 64 | notificationBuilder.setContentText( 65 | String.format(context.getString(R.string.result_notification_content), feeds)) 66 | 67 | notificationBuilder.setAutoCancel(true) 68 | 69 | val intent = Intent(context, OverviewActivity::class.java) 70 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 71 | intent.putExtra(OverviewActivity.CURRENT_FRAGMENT, R.id.nav_results) 72 | 73 | notificationBuilder.setContentIntent( 74 | PendingIntent.getActivity(context, 0, intent, 75 | PendingIntent.FLAG_IMMUTABLE)) 76 | 77 | notificationBuilder.setAutoCancel(true) 78 | 79 | val notificationManager = NotificationManagerCompat.from(context) 80 | notificationManager.notify(FilterFeedsJob.NOTIFICATION_ID, notificationBuilder.build()) 81 | } 82 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/ResourceTracker.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | import java.net.HttpURLConnection 21 | import java.util.* 22 | 23 | /** 24 | * Resource tracker for closeable resources based on a 25 | * [thread](https://discuss.kotlinlang.org/t/kotlin-needs-try-with-resources/214/20) 26 | * in the kotlin language forum. 27 | * @author zouroboros 28 | */ 29 | class ResourceTracker : AutoCloseable { 30 | private val resources = LinkedList() 31 | private val connections = LinkedList() 32 | 33 | fun T.track(): T { 34 | resources.add(this) 35 | return this 36 | } 37 | 38 | fun T.track(): HttpURLConnection { 39 | connections.add(this) 40 | return this 41 | } 42 | 43 | override fun close() { 44 | resources.forEach { it.close() } 45 | connections.forEach { it.inputStream.close(); it.disconnect()} 46 | } 47 | 48 | } 49 | 50 | /** 51 | * Using block in which resources can be tracked using the [ResourceTracker.track] function 52 | * @see [ResourceTracker.track] 53 | */ 54 | fun using(block: ResourceTracker.() -> R): R { 55 | val holder = ResourceTracker() 56 | try { 57 | return holder.block() 58 | } finally { 59 | holder.close() 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/Settings.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | /** 21 | * @author zouroboros 22 | */ 23 | interface Settings { 24 | val showNotifications: Boolean 25 | val backgroundScanning: Boolean 26 | val scanIntervalTableId: Int 27 | val scanIntervalId: Int 28 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/Texts.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher 19 | 20 | import java.net.MalformedURLException 21 | import java.net.URL 22 | import kotlin.math.max 23 | 24 | /** 25 | * Utility methods for texts 26 | * @author zouroboros 27 | */ 28 | object Texts { 29 | 30 | /** 31 | * Shortens a text to text prefix that is most maxLength long 32 | * @param text The string to shorten 33 | * @param maxLength the maximum length of the preview 34 | * @param suffix a suffix to be appended to the shortened string 35 | */ 36 | fun preview(text: String, maxLength: Int, suffix: String): String { 37 | if(text.length <= maxLength) { 38 | return text 39 | } 40 | val lastSpace = text.substring(0, maxLength - suffix.length).lastIndexOf(" ") 41 | return text.substring(0, lastSpace) + suffix 42 | } 43 | 44 | /** 45 | * Looks for urls in the given text and returns the first one. 46 | * The URL needs to start with a protocol 47 | * @text the text in which to look 48 | */ 49 | fun findUrl(text: String): URL? { 50 | val words = text.split(Regex("\\s")); 51 | 52 | return words.firstNotNullOfOrNull { 53 | try { URL(it) } 54 | catch (e: MalformedURLException) { null } } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/AboutActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import android.os.Bundle 21 | import me.murks.feedwatcher.databinding.ActivityAboutBinding 22 | 23 | class AboutActivity : FeedWatcherBaseActivity() { 24 | private lateinit var binding: ActivityAboutBinding 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | binding = ActivityAboutBinding.inflate(layoutInflater) 29 | binding.aboutWebView.loadUrl("file:///android_asset/about/about.html") 30 | setContentView(binding.root) 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/Activities.kt: -------------------------------------------------------------------------------- 1 | package me.murks.feedwatcher.activities 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import me.murks.feedwatcher.R 7 | 8 | /** 9 | * Starts the specified activity 10 | * @author zouroboros 11 | */ 12 | fun Context.startActivity(activityClass: Class) { 13 | val intent = Intent(this, activityClass) 14 | startActivity(intent) 15 | } 16 | 17 | /** 18 | * Opens the feeds list in the overview activity 19 | */ 20 | fun Context.openFeeds() { 21 | val intent = Intent(this, OverviewActivity::class.java) 22 | intent.putExtra(OverviewActivity.CURRENT_FRAGMENT, R.id.nav_feeds) 23 | startActivity(intent) 24 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/DateTimePickerDialogFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import android.app.Dialog 21 | import android.content.DialogInterface 22 | import android.os.Bundle 23 | import android.view.View 24 | import android.widget.DatePicker 25 | import android.widget.TimePicker 26 | import androidx.appcompat.app.AlertDialog 27 | import androidx.fragment.app.DialogFragment 28 | import me.murks.feedwatcher.R 29 | import java.util.* 30 | 31 | /** 32 | * @author zouroboros 33 | */ 34 | class DateTimePickerDialogFragment: DialogFragment() { 35 | 36 | var listener: DateTimePickerDialogListener? = null 37 | 38 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 39 | val view = View.inflate(context, R.layout.layout_date_time_picker, null) 40 | val datePicker = view.findViewById(R.id.date_time_date_picker) 41 | val timePicker = view.findViewById(R.id.date_time_time_picker) 42 | timePicker.visibility = View.GONE 43 | 44 | val date = Calendar.getInstance() 45 | datePicker.init(date.get(Calendar.YEAR), date.get(Calendar.MONTH), 46 | date.get(Calendar.DAY_OF_MONTH)) { v, _, _, _ -> 47 | v.visibility = View.GONE 48 | timePicker.visibility = View.VISIBLE 49 | } 50 | 51 | val builder = AlertDialog.Builder(requireContext()) 52 | .setView(view) 53 | .setPositiveButton(R.string.date_time_confirm, DialogInterface.OnClickListener 54 | { _, _ -> 55 | val selectedDate = Calendar.getInstance() 56 | selectedDate.set(datePicker.year, datePicker.month, datePicker.dayOfMonth, 57 | timePicker.hour, timePicker.minute) 58 | listener?.dateTimeSelected(selectedDate) 59 | }) 60 | .setNegativeButton(R.string.date_time_cancel, DialogInterface.OnClickListener 61 | { _, _ -> }) 62 | return builder.create() 63 | } 64 | 65 | interface DateTimePickerDialogListener { 66 | fun dateTimeSelected(date: Calendar) 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/Dialogs.kt: -------------------------------------------------------------------------------- 1 | package me.murks.feedwatcher.activities 2 | 3 | import android.app.AlertDialog 4 | import android.content.Context 5 | import android.content.DialogInterface 6 | import me.murks.feedwatcher.R 7 | 8 | /** 9 | * Shows an error message to the user 10 | * @author zouroboros 11 | */ 12 | fun Context.errorDialog(errorResourceId: Int, additionalInfos: String, listener: DialogInterface.OnClickListener? = null){ 13 | val builder = AlertDialog.Builder(this) 14 | builder.setMessage(this.resources.getString(errorResourceId, additionalInfos)) 15 | builder.setPositiveButton(R.string.error_dialog_ok_button) { dialog, which -> 16 | dialog.cancel() 17 | listener?.onClick(dialog, which) 18 | } 19 | builder.create().show() 20 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/FeedExportRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import android.view.LayoutInflater 21 | import android.view.View 22 | import android.view.ViewGroup 23 | import me.murks.feedwatcher.databinding.ActivityFeedExportListItemBinding 24 | import me.murks.feedwatcher.R 25 | import me.murks.feedwatcher.model.Feed 26 | 27 | /** 28 | * Adapter for selecting feeds for import 29 | * @author zouroboros 30 | */ 31 | class FeedExportRecyclerViewAdapter(feeds: List): 32 | SelectableRecyclerViewAdapter(feeds) { 33 | 34 | inner class ViewHolder(mView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(mView) { 35 | private val binding = ActivityFeedExportListItemBinding.bind(mView) 36 | val text = binding.activityFeedExportListItemText 37 | val checkBox = binding.activityFeedExportListItemCheckbox 38 | } 39 | 40 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 41 | val view = LayoutInflater.from(parent.context) 42 | .inflate(R.layout.activity_feed_export_list_item, parent, false) 43 | 44 | return ViewHolder(view) 45 | } 46 | 47 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 48 | holder.text.text = items[position].name 49 | holder.checkBox.isChecked = selectedItems.contains(items[position]) 50 | holder.checkBox.setOnCheckedChangeListener { _, isChecked -> 51 | if(isChecked) { 52 | select(holder.bindingAdapterPosition) 53 | } else { 54 | deselect(holder.bindingAdapterPosition) 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/FeedImportFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import android.app.Activity 21 | import android.content.Intent 22 | import android.os.Bundle 23 | import androidx.fragment.app.Fragment 24 | import android.view.LayoutInflater 25 | import android.view.View 26 | import android.view.ViewGroup 27 | 28 | import me.murks.feedwatcher.R 29 | import me.murks.feedwatcher.databinding.FragmentFeedImportBinding 30 | 31 | class FeedImportFragment : Fragment() { 32 | private lateinit var binding: FragmentFeedImportBinding 33 | 34 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 35 | savedInstanceState: Bundle?): View { 36 | binding = FragmentFeedImportBinding.inflate(inflater, container, false) 37 | 38 | return binding.root 39 | } 40 | 41 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 42 | super.onViewCreated(view, savedInstanceState) 43 | 44 | binding.feedImportFragmentButton.setOnClickListener { 45 | val intent = Intent() 46 | intent.action = Intent.ACTION_OPEN_DOCUMENT 47 | intent.type = "*/*" 48 | startActivityForResult(Intent.createChooser(intent, 49 | resources.getString(R.string.feed_import_chooser_title)), OverviewActivity.FEED_IMPORT_SELECT_FILE_REQUEST_CODE) 50 | } 51 | } 52 | 53 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 54 | super.onActivityResult(requestCode, resultCode, data) 55 | if (requestCode == OverviewActivity.FEED_IMPORT_SELECT_FILE_REQUEST_CODE) { 56 | if(resultCode == Activity.RESULT_OK) { 57 | val selectedFile = data?.data 58 | val intent = Intent(context, FeedImportActivity::class.java) 59 | intent.data = selectedFile 60 | startActivity(intent) 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/FeedImportRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import android.view.LayoutInflater 21 | import android.view.View 22 | import android.view.ViewGroup 23 | 24 | import me.murks.feedwatcher.databinding.ActivityFeedImportListItemBinding 25 | import me.murks.feedwatcher.R 26 | import me.murks.jopl.OpOutline 27 | 28 | /** 29 | * Adapter for selecting feeds for import 30 | * @author zouroboros 31 | */ 32 | class FeedImportRecyclerViewAdapter(outlines: List): 33 | SelectableRecyclerViewAdapter(outlines) { 34 | 35 | inner class ViewHolder(mView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(mView) { 36 | private val binding = ActivityFeedImportListItemBinding.bind(mView) 37 | val text = binding.activityFeedImportListItemText 38 | val checkBox = binding.activityFeedImportListItemCheckbox 39 | } 40 | 41 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 42 | val view = LayoutInflater.from(parent.context) 43 | .inflate(R.layout.activity_feed_import_list_item, parent, false) 44 | 45 | return ViewHolder(view) 46 | } 47 | 48 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 49 | holder.text.text = items[position].title 50 | holder.checkBox.isChecked = selectedItems.contains(items[position]) 51 | holder.checkBox.setOnCheckedChangeListener { _, isChecked -> 52 | if(isChecked) { 53 | select(holder.bindingAdapterPosition) 54 | } else { 55 | deselect(holder.bindingAdapterPosition) 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/FeedUiContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 - 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import me.murks.feedwatcher.atomrss.FeedParser 21 | import me.murks.feedwatcher.model.Feed 22 | import me.murks.feedwatcher.model.Scan 23 | import java.net.URL 24 | import java.util.* 25 | 26 | /** 27 | * Container holding all information shown in the ui for a feed 28 | * @author zouroboros 29 | */ 30 | // TODO optimize for lazyfeed parser 31 | data class FeedUiContainer(val feed: Feed?, val name: String, val icon: URL?, 32 | val description: String?, val url: URL, val updated: Date?, 33 | val scans: Collection) { 34 | 35 | constructor(feed: Feed, feedParser: FeedParser, scans: Collection): 36 | this(feed, feed.name, 37 | if (feedParser.iconUrl != null) 38 | feed.url.toURI().resolve(feedParser.iconUrl)?.toURL() else null, 39 | feedParser.description, feed.url, feed.lastUpdate, scans) 40 | 41 | constructor(feed: Feed, scans: Collection): 42 | this(feed, feed.name, null, null, feed.url, feed.lastUpdate, scans) 43 | 44 | constructor(url: URL, updated: Date?, feed: FeedParser): 45 | this(null, feed.name ?: url.toString(), 46 | if (feed.iconUrl != null) url.toURI().resolve(feed.iconUrl)?.toURL() else null, 47 | feed.description, url, updated, emptyList()) 48 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/FeedWatcherAsyncLoadingFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2020 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import android.os.Handler 21 | import android.os.Looper 22 | 23 | /** 24 | * Base class for fragments that load data asynchronously. 25 | * @author zouroboros 26 | */ 27 | abstract class FeedWatcherAsyncLoadingFragment: FeedWatcherBaseFragment() { 28 | private var loadingThread: Thread? = null 29 | val handler = Handler(Looper.getMainLooper()) 30 | 31 | private fun createThread() = Thread { 32 | handler.post { onLoadingStart() } 33 | loadData() 34 | handler.post { onLoadingFinished() } 35 | } 36 | 37 | abstract fun loadData() 38 | 39 | abstract fun processResult(result: TResult) 40 | 41 | abstract fun onLoadingStart() 42 | 43 | abstract fun onLoadingFinished() 44 | 45 | protected fun reload() { 46 | if (!(loadingThread?.isAlive == true)) { 47 | loadingThread = createThread() 48 | loadingThread!!.start() 49 | } 50 | } 51 | 52 | protected fun publishResult(result: TResult) { 53 | handler.post { processResult(result) } 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/FeedWatcherBaseActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import android.os.Bundle 21 | import androidx.appcompat.app.AppCompatActivity 22 | import me.murks.feedwatcher.AndroidEnvironment 23 | import me.murks.feedwatcher.FeedWatcherApp 24 | 25 | /** 26 | * Base activity for all activities of the feed watcher app. 27 | * Provides common fields and methods 28 | * @author zouroboros 29 | */ 30 | abstract class FeedWatcherBaseActivity: AppCompatActivity() { 31 | lateinit var app: FeedWatcherApp 32 | 33 | override fun onCreate(savedInstanceState: Bundle?) { 34 | super.onCreate(savedInstanceState) 35 | app = FeedWatcherApp(AndroidEnvironment(this)) 36 | } 37 | 38 | override fun onDestroy() { 39 | super.onDestroy() 40 | app.environment.close() 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/FeedWatcherBaseFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import android.os.Bundle 21 | import me.murks.feedwatcher.AndroidEnvironment 22 | import me.murks.feedwatcher.FeedWatcherApp 23 | 24 | /** 25 | * @author zouroboros 26 | */ 27 | abstract class FeedWatcherBaseFragment: androidx.fragment.app.Fragment() { 28 | lateinit var app: FeedWatcherApp 29 | 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | super.onCreate(savedInstanceState) 32 | app = FeedWatcherApp(AndroidEnvironment(requireContext())) 33 | } 34 | 35 | override fun onDestroy() { 36 | app.environment.close() 37 | super.onDestroy() 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/FeedsRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 - 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import android.graphics.Bitmap 21 | import android.view.LayoutInflater 22 | import android.view.View 23 | import android.view.ViewGroup 24 | import android.widget.ImageView 25 | import android.widget.TextView 26 | import androidx.core.content.ContextCompat 27 | 28 | import me.murks.feedwatcher.R 29 | import me.murks.feedwatcher.databinding.FragmentFeedsListItemBinding 30 | import me.murks.feedwatcher.model.Feed 31 | import me.murks.feedwatcher.tasks.Tasks 32 | import java.net.URL 33 | 34 | /** 35 | * Adapter for displaying a list of feeds 36 | * @see [Feed] 37 | * @author zouroboros 38 | */ 39 | class FeedsRecyclerViewAdapter(listener: FeedListInteractionListener?) 40 | : ListRecyclerViewAdapter(listOf()) { 41 | 42 | private val iconsLoading = mutableSetOf() 43 | 44 | private val onClickListener: View.OnClickListener = View.OnClickListener { v -> 45 | val item = v.tag as FeedUiContainer 46 | listener?.onOpenFeed(item) 47 | } 48 | private val images: MutableMap = mutableMapOf() 49 | 50 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 51 | val view = LayoutInflater.from(parent.context) 52 | .inflate(R.layout.fragment_feeds_list_item, parent, false) 53 | return ViewHolder(view) 54 | } 55 | 56 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 57 | val item = items[position] 58 | holder.feedName.text = item.name 59 | holder.feedIcon.setImageResource(R.drawable.ic_feed_icon) 60 | if(item.icon != null && images.containsKey(item.icon)) { 61 | holder.feedIcon.setImageBitmap(images[item.icon]) 62 | } else if (item.icon != null && !iconsLoading.contains(item.icon)) { 63 | iconsLoading.add(item.icon) 64 | Tasks.loadImage(item.icon, holder.feedIcon.layoutParams.width, holder.feedIcon.layoutParams.height) 65 | .thenAcceptAsync({ 66 | val index = items.indexOfFirst { item.icon == it.icon } 67 | images[item.icon] = it 68 | notifyItemChanged(index) 69 | }, ContextCompat.getMainExecutor(holder.itemView.context)) 70 | } 71 | 72 | with(holder.mView) { 73 | tag = item 74 | setOnClickListener(onClickListener) 75 | } 76 | } 77 | 78 | inner class ViewHolder(val mView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(mView) { 79 | private val binding = FragmentFeedsListItemBinding.bind(mView) 80 | val feedName: TextView = binding.feedName 81 | val feedIcon: ImageView = binding.feedIcon 82 | } 83 | 84 | interface FeedListInteractionListener { 85 | fun onOpenFeed(feed: FeedUiContainer) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/FilterUiModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import me.murks.feedwatcher.model.* 21 | import java.lang.IllegalStateException 22 | import java.util.* 23 | 24 | /** 25 | * @author zouroboros 26 | */ 27 | class FilterUiModel(var type: FilterType, val feeds: List): FilterTypeCallback { 28 | var containsText: String = "" 29 | var selectedType: Int 30 | get() = FilterType.values().indexOf(type) 31 | set(value) { type = FilterType.values()[value] } 32 | var selectedFeed: Int? = null 33 | var startDate: Date = Date() 34 | val feedNames = feeds.map { it.name } 35 | 36 | constructor(filter: Filter, feeds: List): this(filter.type, feeds) { 37 | filter.filterCallback(this) 38 | } 39 | 40 | fun filter(index: Int): Filter { 41 | if(type == FilterType.CONTAINS) { 42 | return ContainsFilter(index, containsText) 43 | } else if(type == FilterType.FEED) { 44 | return FeedFilter(index, if (selectedFeed != null) feeds[selectedFeed!!].url else null) 45 | } else if(type == FilterType.NEW) { 46 | return NewEntryFilter(index, startDate) 47 | } 48 | throw IllegalStateException() 49 | } 50 | 51 | override fun filter(filter: FeedFilter) { 52 | selectedFeed = feeds.map { it.url }.indexOf(filter.feedUrl) 53 | } 54 | 55 | override fun filter(filter: ContainsFilter) { 56 | containsText = filter.text?: "" 57 | } 58 | 59 | override fun filter(filter: NewEntryFilter) { 60 | startDate = filter.start 61 | } 62 | 63 | 64 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/Formatter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import android.content.Context 21 | import android.text.format.DateFormat 22 | import java.util.* 23 | 24 | /** 25 | * Formats value into strings that can be shown in the ui 26 | * @author zouroboros 27 | */ 28 | object Formatter { 29 | @JvmStatic 30 | fun dateToString(context: Context, value: Date): String { 31 | return "${DateFormat.getDateFormat(context).format(value)} " + 32 | DateFormat.getTimeFormat(context).format(value) 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/ListRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import androidx.recyclerview.widget.RecyclerView 21 | import java.util.* 22 | 23 | /** 24 | * Base class for list adapter 25 | * @author zouroboros 26 | */ 27 | abstract class ListRecyclerViewAdapter(items: List): 28 | RecyclerView.Adapter() { 29 | 30 | private var list: MutableList = LinkedList(items) 31 | 32 | var items: MutableList 33 | set(value) { 34 | list = value 35 | notifyDataSetChanged() 36 | } 37 | get() = list 38 | 39 | final override fun getItemCount() = items.size 40 | 41 | fun append(item: I) { 42 | list.add(item) 43 | notifyItemInserted(list.size - 1) 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/PreferencesActivity.kt: -------------------------------------------------------------------------------- 1 | package me.murks.feedwatcher.activities 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.widget.Toast 7 | import me.murks.feedwatcher.R 8 | import me.murks.feedwatcher.databinding.PreferencesActivityBinding 9 | import me.murks.feedwatcher.tasks.Tasks 10 | import java.io.FileOutputStream 11 | 12 | class PreferencesActivity : FeedWatcherBaseActivity() { 13 | 14 | companion object { 15 | const val DATABASE_EXPORT_SELECT_FILE_REQUEST_CODE = 1234 16 | } 17 | 18 | private lateinit var binding: PreferencesActivityBinding 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | binding = PreferencesActivityBinding.inflate(layoutInflater) 23 | setContentView(binding.root) 24 | 25 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 26 | 27 | binding.openAboutActivityButton.setOnClickListener { 28 | val intent = Intent(this, AboutActivity::class.java) 29 | startActivity(intent) 30 | } 31 | 32 | binding.exportDatabaseButton.setOnClickListener { 33 | val intent = Intent() 34 | intent.action = Intent.ACTION_CREATE_DOCUMENT 35 | intent.type = "*/*" 36 | startActivityForResult(Intent.createChooser(intent, 37 | resources.getString(R.string.export_database)), 38 | DATABASE_EXPORT_SELECT_FILE_REQUEST_CODE) 39 | } 40 | } 41 | 42 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 43 | super.onActivityResult(requestCode, resultCode, data) 44 | 45 | if (requestCode == DATABASE_EXPORT_SELECT_FILE_REQUEST_CODE) { 46 | if (resultCode == Activity.RESULT_OK) { 47 | val selectedFile = data!!.data!! 48 | Tasks.run({ 49 | val file = contentResolver.openFileDescriptor(selectedFile, "w") 50 | FileOutputStream(file!!.fileDescriptor).use { 51 | app.exportDatabase(it) 52 | } 53 | }, { 54 | Toast.makeText(this, R.string.database_successfully_exported, Toast.LENGTH_SHORT).show() 55 | }, { 56 | Toast.makeText(this, R.string.database_export_failed, Toast.LENGTH_SHORT).show() 57 | app.environment.log.error("Database export to ${selectedFile.path} failed.", it) 58 | }).execute(Unit) 59 | 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/PreferencesFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 - 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | 21 | import android.content.SharedPreferences 22 | import android.os.Bundle 23 | import androidx.preference.Preference 24 | import androidx.preference.PreferenceFragmentCompat 25 | import androidx.preference.SeekBarPreference 26 | import me.murks.feedwatcher.AndroidEnvironment 27 | import me.murks.feedwatcher.Constants 28 | import me.murks.feedwatcher.FeedWatcherApp 29 | import me.murks.feedwatcher.R 30 | 31 | class PreferencesFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener, 32 | SharedPreferences.OnSharedPreferenceChangeListener { 33 | 34 | private lateinit var app: FeedWatcherApp 35 | 36 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 37 | setPreferencesFromResource(R.xml.preferences, rootKey) 38 | 39 | app = FeedWatcherApp(AndroidEnvironment(requireContext())) 40 | 41 | val scanInterval = findPreference(Constants.scanIntervalIdPreferencesKey)!! 42 | val intervals = app.getCurrentScanIntervals() 43 | scanInterval.onPreferenceChangeListener = this 44 | scanInterval.min = 0 45 | scanInterval.max = intervals.size - 1 46 | 47 | val minutesSummary = resources.getString(R.string.preferences_scanning_interval_summary_minutes) 48 | val hoursSummary = resources.getString(R.string.preferences_scanning_interval_summary_hours) 49 | val hoursMinutes = resources.getString(R.string.preferences_scanning_interval_summary_hours_minutes) 50 | 51 | scanInterval.summaryProvider = Preference.SummaryProvider { 52 | val interval = intervals[it.value] 53 | when { 54 | interval.hours == 0 -> String.format(minutesSummary, interval.minutes) 55 | interval.minutes == 0 -> String.format(hoursSummary, interval.hours) 56 | else -> String.format(hoursMinutes, interval.hours, interval.minutes) 57 | } 58 | } 59 | 60 | val scanIntervalInfo = findPreference(Constants.scanIntervalInfo)!! 61 | scanIntervalInfo.title = String.format(scanIntervalInfo.title.toString(), 62 | app.environment.jobs.minimumInterval / 1000 / 60, 63 | app.environment.jobs.intervalAccuracy / 1000 / 60) 64 | } 65 | 66 | override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean { 67 | if(preference is SeekBarPreference) { 68 | // the SeekBarPreference is not updating its summary by itself -> we need to do it manually 69 | preference.value = newValue as Int 70 | } 71 | return true 72 | } 73 | 74 | override fun onResume() { 75 | super.onResume() 76 | preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this) 77 | } 78 | 79 | override fun onPause() { 80 | super.onPause() 81 | preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this) 82 | } 83 | 84 | override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { 85 | when (key) { 86 | Constants.scanIntervalIdPreferencesKey -> app.rescheduleJobs() 87 | Constants.backgroundScanningPreferencesKey -> app.rescheduleJobs() 88 | } 89 | } 90 | 91 | override fun onDestroy() { 92 | super.onDestroy() 93 | app.environment.close() 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/QueriesFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019-2020 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import android.content.Context 21 | import android.os.Bundle 22 | import android.view.LayoutInflater 23 | import android.view.View 24 | import android.view.ViewGroup 25 | import android.widget.ProgressBar 26 | import androidx.recyclerview.widget.DividerItemDecoration 27 | import androidx.recyclerview.widget.ItemTouchHelper 28 | import androidx.recyclerview.widget.RecyclerView 29 | import me.murks.feedwatcher.R 30 | import me.murks.feedwatcher.model.Query 31 | 32 | /** 33 | * A fragment representing a list of Queries. 34 | */ 35 | class QueriesFragment : FeedWatcherAsyncLoadingFragment() { 36 | 37 | private var listener: OnListFragmentInteractionListener? = null 38 | private lateinit var adapter: QueryRecyclerViewAdapter 39 | private lateinit var progressBar: ProgressBar 40 | 41 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, 42 | savedInstanceState: Bundle?): View? { 43 | val view = inflater.inflate(R.layout.fragment_queries_list, container, false) 44 | progressBar = view.findViewById(R.id.queries_fragment_progress_bar) 45 | val queryList = view.findViewById(R.id.queries_fragment_query_list) 46 | 47 | adapter = QueryRecyclerViewAdapter(app.queries(), listener) 48 | 49 | queryList.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context) 50 | queryList.adapter = adapter 51 | 52 | queryList.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) 53 | 54 | val swipeHelper = ItemTouchHelper( 55 | object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) { 56 | override fun onMove(recyclerView: RecyclerView, 57 | viewHolder: RecyclerView.ViewHolder, 58 | target: RecyclerView.ViewHolder): Boolean { 59 | return false 60 | } 61 | 62 | override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { 63 | app.delete(adapter.items[viewHolder.adapterPosition]) 64 | adapter.items.removeAt(viewHolder.adapterPosition) 65 | adapter.notifyItemRemoved(viewHolder.adapterPosition) 66 | } 67 | }) 68 | 69 | swipeHelper.attachToRecyclerView(queryList) 70 | 71 | return view 72 | } 73 | 74 | override fun onAttach(context: Context) { 75 | super.onAttach(context) 76 | if (context is OnListFragmentInteractionListener) { 77 | listener = context 78 | } 79 | } 80 | 81 | override fun onDetach() { 82 | super.onDetach() 83 | listener = null 84 | } 85 | 86 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 87 | super.onViewCreated(view, savedInstanceState) 88 | reload() 89 | } 90 | 91 | override fun onResume() { 92 | super.onResume() 93 | reload() 94 | } 95 | 96 | interface OnListFragmentInteractionListener { 97 | fun onOpenQuery(item: Query) 98 | } 99 | 100 | override fun loadData() { 101 | for (query in app.queries()) { 102 | publishResult(query) 103 | } 104 | } 105 | 106 | override fun processResult(result: Query) { 107 | adapter.append(result) 108 | } 109 | 110 | override fun onLoadingStart() { 111 | progressBar.visibility = View.VISIBLE 112 | adapter.items.clear() 113 | adapter.notifyDataSetChanged() 114 | } 115 | 116 | override fun onLoadingFinished() { 117 | progressBar.visibility = View.GONE 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/QueryRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package me.murks.feedwatcher.activities 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.TextView 7 | 8 | import me.murks.feedwatcher.R 9 | import me.murks.feedwatcher.databinding.FragmentQueriesListItemBinding 10 | 11 | import me.murks.feedwatcher.activities.QueriesFragment.OnListFragmentInteractionListener 12 | 13 | import me.murks.feedwatcher.model.Query 14 | 15 | class QueryRecyclerViewAdapter(queries: List, 16 | private val listener: OnListFragmentInteractionListener?) 17 | : ListRecyclerViewAdapter(queries) { 18 | 19 | private val onClickListener: View.OnClickListener 20 | 21 | init { 22 | onClickListener = View.OnClickListener { v -> 23 | val item = v.tag as Query 24 | listener?.onOpenQuery(item) 25 | } 26 | } 27 | 28 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 29 | val view = LayoutInflater.from(parent.context) 30 | .inflate(R.layout.fragment_queries_list_item, parent, false) 31 | return ViewHolder(view) 32 | } 33 | 34 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 35 | val item = items[position] 36 | holder.name.text = item.name 37 | 38 | with(holder.mView) { 39 | tag = item 40 | setOnClickListener(onClickListener) 41 | } 42 | } 43 | 44 | inner class ViewHolder(val mView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(mView) { 45 | val binding = FragmentQueriesListItemBinding.bind(mView) 46 | val name: TextView = binding.queryName 47 | 48 | override fun toString(): String { 49 | return super.toString() + " '" + name.text + "'" 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/ResultActivity.kt: -------------------------------------------------------------------------------- 1 | package me.murks.feedwatcher.activities 2 | 3 | import android.os.Bundle 4 | import android.text.format.DateFormat 5 | import android.webkit.WebView 6 | import android.widget.TextView 7 | import me.murks.feedwatcher.HtmlTags 8 | import me.murks.feedwatcher.R 9 | import me.murks.feedwatcher.itemDescription 10 | 11 | class ResultActivity : FeedWatcherBaseActivity() { 12 | 13 | private lateinit var resultName: TextView 14 | private lateinit var resultDescription: WebView 15 | private lateinit var resultFeed: TextView 16 | private lateinit var resultDate: TextView 17 | private lateinit var resultLink: TextView 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | setContentView(R.layout.activity_result) 22 | 23 | resultName = findViewById(R.id.result_result_name) 24 | resultDescription = findViewById(R.id.result_result_description) 25 | resultFeed = findViewById(R.id.result_result_feed) 26 | resultDate = findViewById(R.id.result_result_date) 27 | resultLink = findViewById(R.id.result_result_link) 28 | 29 | val resultId = intent.getLongExtra(RESULT_EXTRA_NAME, -1) 30 | val result = app.result(resultId) 31 | 32 | resultName.text = result.item.title 33 | resultDescription.loadData(HtmlTags.wrapInDocument(result.item.itemDescription(this), 34 | backgroundColor()), "text/html", "UTF-8") 35 | resultFeed.text = result.feed.name 36 | resultDate.text = DateFormat.getDateFormat(this).format(result.found) + 37 | " " + DateFormat.getTimeFormat(this).format(result.found) 38 | resultLink.text = result.item.link?.resolve(result.feed.url.toURI())?.toString() ?: "" 39 | 40 | app.markAsRead(result) 41 | } 42 | 43 | private fun backgroundColor(): Int { 44 | val array = theme.obtainStyledAttributes(arrayOf(android.R.attr.colorBackground).toIntArray()) 45 | return array.getColor(0, 0) 46 | } 47 | 48 | companion object { 49 | const val RESULT_EXTRA_NAME = "result" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/ResultsRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 - 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import android.graphics.Typeface 21 | import android.text.format.DateFormat 22 | import android.view.LayoutInflater 23 | import android.view.View 24 | import android.view.ViewGroup 25 | import me.murks.feedwatcher.* 26 | 27 | import me.murks.feedwatcher.databinding.FragmentResultsListItemBinding 28 | import me.murks.feedwatcher.activities.ResultsFragment.OnListFragmentInteractionListener 29 | import me.murks.feedwatcher.model.Result 30 | 31 | /** 32 | * Adapter for displaying a list of results 33 | */ 34 | class ResultsRecyclerViewAdapter( 35 | results: List, 36 | private val listener: OnListFragmentInteractionListener?) 37 | : ListRecyclerViewAdapter(results) { 38 | 39 | private val onClickListener: View.OnClickListener 40 | 41 | init { 42 | onClickListener = View.OnClickListener { v -> 43 | val item = v.tag as Result 44 | listener?.onOpenResult(item) 45 | } 46 | } 47 | 48 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 49 | val view = LayoutInflater.from(parent.context) 50 | .inflate(R.layout.fragment_results_list_item, parent, false) 51 | return ViewHolder(view) 52 | } 53 | 54 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 55 | val result = items[position] 56 | holder.resultName.text = result.item.title 57 | holder.resultDescription.text = Texts.preview( 58 | HtmlTags.text(result.item.itemDescription(holder.mView.context)), 200, "...") 59 | holder.resultFeed.text = result.feed.name 60 | holder.resultDate.text = DateFormat.getDateFormat(holder.mView.context).format(result.found) 61 | 62 | if (result.unread) { 63 | holder.resultName.typeface = Typeface.defaultFromStyle(Typeface.BOLD) 64 | holder.resultDescription.typeface = Typeface.defaultFromStyle(Typeface.BOLD) 65 | holder.resultFeed.typeface = Typeface.defaultFromStyle(Typeface.BOLD) 66 | holder.resultDate.typeface = Typeface.defaultFromStyle(Typeface.BOLD) 67 | holder.foundIn.typeface = Typeface.defaultFromStyle(Typeface.BOLD) 68 | } else { 69 | holder.resultName.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) 70 | holder.resultDescription.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) 71 | holder.resultFeed.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) 72 | holder.resultDate.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) 73 | holder.foundIn.typeface = Typeface.defaultFromStyle(Typeface.NORMAL) 74 | } 75 | 76 | with(holder.mView) { 77 | tag = result 78 | setOnClickListener(onClickListener) 79 | } 80 | } 81 | 82 | inner class ViewHolder(val mView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(mView) { 83 | private val binding = FragmentResultsListItemBinding.bind(mView) 84 | val resultName = binding.resultsResultName 85 | val resultDescription = binding.resultsResultDescription 86 | val resultFeed = binding.resultsResultFeed 87 | val resultDate = binding.resultsResultDate 88 | val foundIn = binding.resultsFoundIn 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/activities/SelectableRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.activities 19 | 20 | import androidx.recyclerview.widget.RecyclerView 21 | 22 | /** 23 | * Abstract class that provides a basic framework for selectable lists. 24 | * 25 | * @author zouroboros 26 | */ 27 | abstract class SelectableRecyclerViewAdapter( 28 | items: List): 29 | ListRecyclerViewAdapter(items) { 30 | 31 | val selectedItems = mutableSetOf() 32 | 33 | fun select(itemIndex: Int) { 34 | if(selectedItems.add(items[itemIndex])) { 35 | notifyItemChanged(itemIndex) 36 | } 37 | } 38 | 39 | fun deselect(itemIndex: Int) { 40 | if(selectedItems.remove(items[itemIndex])) { 41 | notifyItemChanged(itemIndex) 42 | } 43 | } 44 | 45 | fun selectAll() { 46 | if(selectedItems.addAll(items)) { 47 | notifyDataSetChanged() 48 | } 49 | } 50 | 51 | fun deselectAll() { 52 | if(selectedItems.removeAll(items)) { 53 | notifyDataSetChanged() 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/data/AddFeeds.kt: -------------------------------------------------------------------------------- 1 | package me.murks.feedwatcher.data 2 | 3 | import me.murks.feedwatcher.model.Feed 4 | 5 | /** 6 | * Unit of Work for adding feeds 7 | * @author zouroboros 8 | */ 9 | class AddFeeds(private val feeds: Collection): UnitOfWork { 10 | override fun execute(store: DataStore) { 11 | store.startTransaction() 12 | feeds.forEach { feed -> 13 | store.addFeed(feed) 14 | } 15 | store.commitTransaction() 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/data/ClearResults.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2020 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.data 19 | 20 | /** 21 | * @author zouroboros 22 | */ 23 | class ClearResults : UnitOfWork{ 24 | override fun execute(store: DataStore) { 25 | store.startTransaction() 26 | for (result in store.getResults()) { 27 | store.delete(result) 28 | } 29 | store.commitTransaction() 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/data/Cursors.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2020 - 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.data 19 | 20 | import android.database.Cursor 21 | import java.util.* 22 | 23 | /** 24 | * Utility functions for {@see android.database.Cursor} objects 25 | * @author zouroboros 26 | */ 27 | 28 | /** 29 | * Reads the values of one column into a list 30 | */ 31 | fun Cursor.getColumnValues(columnName: String, f: Function2): Collection { 32 | val values = LinkedList() 33 | 34 | while (this.moveToNext()) { 35 | values.add(f(this, this.getColumnIndex(columnName))) 36 | } 37 | 38 | return values 39 | } 40 | 41 | fun Cursor.selectCount(): Int { 42 | return this.use { 43 | it.moveToFirst() 44 | return@use it.getInt(0) 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/data/DeleteFeed.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.data 19 | 20 | import me.murks.feedwatcher.model.Feed 21 | 22 | /** 23 | * Implements the logic for deleting feeds. 24 | * 25 | * @author zouroboros 26 | */ 27 | class DeleteFeed(val feed: Feed): UnitOfWork { 28 | override fun execute(store: DataStore) { 29 | store.startTransaction() 30 | val results = store.getResultsForFeed(feed) 31 | val scans = store.getScansForFeed(feed) 32 | 33 | // in any case we delete all scans 34 | for (scan in scans) { 35 | store.delete(scan) 36 | } 37 | 38 | // now if a feed still has results we only mark the feed as deleted 39 | // if not we can delete the feed directly 40 | if (results.isEmpty()) { 41 | store.delete(feed) 42 | } else { 43 | store.markDeleted(feed) 44 | } 45 | 46 | store.commitTransaction() 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/data/DeleteResult.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2020 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.data 19 | 20 | import me.murks.feedwatcher.model.* 21 | 22 | /** 23 | * @author zouroboros 24 | */ 25 | class DeleteResult(val result: Result) : UnitOfWork { 26 | override fun execute(store: DataStore) { 27 | store.startTransaction() 28 | store.delete(result) 29 | store.commitTransaction() 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/data/FeedWatcherSchema.java: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2020 - 2022 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.data; 19 | 20 | import android.database.sqlite.SQLiteDatabase; 21 | import android.util.Log; 22 | 23 | import me.murks.sqlschemaspec.ColumnSpec; 24 | import me.murks.sqlschemaspec.SchemaSpec; 25 | import me.murks.sqlschemaspec.TableSpec; 26 | import me.murks.sqlschemaspec.Type; 27 | import me.murks.sqlschemaspec.templates.TemplateCompiler; 28 | 29 | /** 30 | * Database schema of the FeedWatcherApp 31 | * @author zouroboros 32 | */ 33 | public class FeedWatcherSchema extends SchemaSpec { 34 | 35 | class Feeds extends TableSpec { 36 | ColumnSpec id = primaryKey(Type.Integer); 37 | ColumnSpec name = column(Type.String); 38 | ColumnSpec url = column(Type.String); 39 | ColumnSpec lastUpdated = column(Type.Integer, true); 40 | ColumnSpec deleted = column(Type.Boolean); 41 | } 42 | 43 | Feeds feeds = new Feeds(); 44 | 45 | class Queries extends TableSpec { 46 | ColumnSpec id = primaryKey(Type.Integer); 47 | ColumnSpec name = column(Type.String); 48 | ColumnSpec deleted = column(Type.Boolean); 49 | } 50 | 51 | Queries queries = new Queries(); 52 | 53 | class Filters extends TableSpec { 54 | ColumnSpec id = primaryKey(Type.Integer); 55 | ColumnSpec type = column(Type.String); 56 | ColumnSpec index = column(Type.Integer); 57 | ColumnSpec queryId = foreignKey(queries.id); 58 | } 59 | 60 | Filters filters = new Filters(); 61 | 62 | /** 63 | * Table that connects filters and their parameter 64 | */ 65 | class FilterParameters extends TableSpec { 66 | ColumnSpec id = primaryKey(Type.Integer); 67 | ColumnSpec name = column(Type.String); 68 | ColumnSpec stringValue = column(Type.String, true); 69 | ColumnSpec filterId = foreignKey(filters.id); 70 | ColumnSpec dateValue = column(Type.Integer, true); 71 | } 72 | 73 | FilterParameters filterParameters = new FilterParameters(); 74 | 75 | class Results extends TableSpec { 76 | ColumnSpec id = primaryKey(Type.Integer); 77 | ColumnSpec feedId = foreignKey(feeds.id); 78 | ColumnSpec title = column(Type.String); 79 | ColumnSpec description = column(Type.String, true); 80 | ColumnSpec link = column(Type.String, true); 81 | ColumnSpec date = column(Type.Integer, true); 82 | ColumnSpec found = column(Type.Integer); 83 | ColumnSpec unread = column(Type.Boolean); 84 | } 85 | 86 | Results results = new Results(); 87 | 88 | /** 89 | * Table that connects results and the query that produced the result. 90 | */ 91 | class ResultQueries extends TableSpec { 92 | ColumnSpec id = primaryKey(Type.Integer); 93 | ColumnSpec resultId = foreignKey(results.id); 94 | ColumnSpec queryId = foreignKey(queries.id); 95 | } 96 | 97 | ResultQueries resultQueries = new ResultQueries(); 98 | 99 | class Scans extends TableSpec { 100 | ColumnSpec id = primaryKey(Type.Integer); 101 | ColumnSpec feedId = foreignKey(feeds.id); 102 | ColumnSpec successfully = column(Type.Boolean); 103 | ColumnSpec errorText = column(Type.String, true); 104 | ColumnSpec scanDate = column(Type.Integer); 105 | } 106 | 107 | Scans scans = new Scans(); 108 | 109 | public FeedWatcherSchema() { 110 | TemplateCompiler compiler = new TemplateCompiler(); 111 | compiler.compileTemplate(this, this); 112 | } 113 | 114 | public void createSchema(SQLiteDatabase db) { 115 | db.beginTransaction(); 116 | for (String statement: createStatement()) { 117 | Log.d(getClass().toString(), statement); 118 | db.execSQL(statement); 119 | } 120 | db.setTransactionSuccessful(); 121 | db.endTransaction(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/data/RecordScan.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.data 19 | 20 | import me.murks.feedwatcher.model.* 21 | 22 | import java.util.* 23 | 24 | /** 25 | * Unit of work to insert the results and mark all feeds as updated. 26 | * @author zouroboros 27 | */ 28 | class RecordScan(private val results: List, private val scans: List, private val updateDate: Date) : UnitOfWork { 29 | /** 30 | * Maximum number of scans that is kept for each feed. 31 | */ 32 | private val MaxScansForFeed = 3 33 | 34 | override fun execute(store: DataStore) { 35 | val feeds = store.getFeeds() 36 | val newFeeds = feeds.map { Feed(it.url, updateDate, it.name) } 37 | 38 | store.startTransaction() 39 | 40 | for (result in results) { 41 | store.addResult(result) 42 | } 43 | 44 | for (feed in newFeeds) { 45 | store.updateFeed(feed) 46 | } 47 | 48 | for (scan in scans) { 49 | store.addScan(scan) 50 | } 51 | 52 | for (feed in feeds) { 53 | val scans = store.getScansForFeed(feed) 54 | if (scans.size > MaxScansForFeed) { 55 | val numberOfScansToDelete = scans.size - MaxScansForFeed 56 | val scansToDelete = scans 57 | .sortedBy { it.scanDate } 58 | .take(numberOfScansToDelete) 59 | for (scan in scansToDelete) { 60 | store.delete(scan) 61 | } 62 | } 63 | } 64 | store.commitTransaction() 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/data/UnitOfWork.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.data 19 | 20 | /** 21 | * Interface for objects that represent units of work for the database e.g: Queries, Inserts, Deletes 22 | * @author zouroboros 23 | */ 24 | interface UnitOfWork { 25 | fun execute(store: DataStore) 26 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/data/UpdateResult.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2022 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.data 19 | 20 | import me.murks.feedwatcher.model.* 21 | 22 | /** 23 | * @author zouroboros 24 | */ 25 | class UpdateResult(val result: Result) : UnitOfWork { 26 | override fun execute(store: DataStore) { 27 | store.startTransaction() 28 | store.update(result) 29 | store.commitTransaction() 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/model/ContainsFilter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 - 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.model 19 | 20 | import me.murks.feedwatcher.atomrss.FeedItem 21 | 22 | class ContainsFilter(index: Int, val text: String?): Filter(FilterType.CONTAINS, index) { 23 | 24 | companion object { 25 | const val textParameterName = "text" 26 | } 27 | 28 | override fun filterItems(feed: Feed, items: List): List { 29 | return items.filter { it.title?.contains(text ?: "", true) == true 30 | || it.description?.contains(text ?: "", true) == true } 31 | } 32 | 33 | override fun filterCallback(callback: FilterTypeCallback): R { 34 | return callback.filter(this) 35 | } 36 | 37 | override fun parameter(): List { 38 | return listOf(FilterParameter(textParameterName, text, null)) 39 | } 40 | 41 | override fun equals(other: Any?): Boolean { 42 | if (this === other) return true 43 | if (javaClass != other?.javaClass) return false 44 | 45 | other as ContainsFilter 46 | 47 | if (text != other.text) return false 48 | 49 | return super.equals(other) 50 | } 51 | 52 | override fun hashCode(): Int { 53 | var result = super.hashCode() 54 | result = 31 * result + text.hashCode() 55 | return result 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/model/Feed.kt: -------------------------------------------------------------------------------- 1 | package me.murks.feedwatcher.model 2 | 3 | import java.net.URL 4 | import java.util.* 5 | 6 | /** 7 | * @author zouroboros 8 | * @date 8/13/18. 9 | */ 10 | data class Feed(val url: URL, val lastUpdate : Date?, val name: String) -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/model/FeedFilter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 - 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.model 19 | 20 | import me.murks.feedwatcher.atomrss.FeedItem 21 | import java.net.URL 22 | 23 | /** 24 | * @author zouroboros 25 | */ 26 | class FeedFilter(index: Int, val feedUrl: URL?): Filter(FilterType.FEED, index) { 27 | 28 | companion object { 29 | val feedUrlParameterName = "feedUrl" 30 | } 31 | 32 | override fun filterItems(feed: Feed, items: List): List { 33 | return if(feed.url == feedUrl) { 34 | items 35 | } else { 36 | emptyList() 37 | } 38 | } 39 | 40 | override fun filterCallback(callback: FilterTypeCallback): R { 41 | return callback.filter(this) 42 | } 43 | 44 | override fun parameter(): List { 45 | return listOf(FilterParameter(feedUrlParameterName, feedUrl?.toString(), null)) 46 | } 47 | 48 | override fun equals(other: Any?): Boolean { 49 | if (this === other) return true 50 | if (javaClass != other?.javaClass) return false 51 | if (!super.equals(other)) return false 52 | 53 | other as FeedFilter 54 | 55 | if (feedUrl != other.feedUrl) return false 56 | 57 | return super.equals(other) 58 | } 59 | 60 | override fun hashCode(): Int { 61 | var result = super.hashCode() 62 | result = 31 * result + feedUrl.hashCode() 63 | return result 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/model/Filter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 - 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.model 19 | 20 | import me.murks.feedwatcher.atomrss.FeedItem 21 | 22 | /** 23 | * Base class for filters 24 | * @author zouroboros 25 | */ 26 | abstract class Filter(val type: FilterType, val index: Int) { 27 | 28 | abstract fun filterItems(feed: Feed, items: List): List 29 | 30 | abstract fun filterCallback(callback: FilterTypeCallback): R 31 | 32 | /** 33 | * Returns the parameter of a filter 34 | */ 35 | abstract fun parameter(): List 36 | 37 | override fun equals(other: Any?): Boolean { 38 | if (this === other) return true 39 | if (javaClass != other?.javaClass) return false 40 | 41 | other as Filter 42 | 43 | if (type != other.type) return false 44 | if (index != other.index) return false 45 | 46 | return true 47 | } 48 | 49 | override fun hashCode(): Int { 50 | var result = type.hashCode() 51 | result = 31 * result + index 52 | return result 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/model/FilterFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.model 19 | 20 | import java.lang.IllegalArgumentException 21 | import java.net.URL 22 | 23 | /** 24 | * Factory for [Filter] 25 | * @author zouroboros 26 | */ 27 | object FilterFactory { 28 | fun new(index: Int, type: FilterType, parameter: List): Filter { 29 | if(type == FilterType.CONTAINS) { 30 | val text = parameter.find { it.name == ContainsFilter.textParameterName }!!.stringValue 31 | return ContainsFilter(index, text) 32 | } else if (type == FilterType.FEED) { 33 | val feedUrl = parameter.find { it.name == FeedFilter.feedUrlParameterName }!!.stringValue 34 | return FeedFilter(index, if (feedUrl != null) URL(feedUrl) else null) 35 | } else if (type == FilterType.NEW) { 36 | val startDate = parameter.find { it.name == NewEntryFilter.startDateParameterName }!!.dateValue!! 37 | return NewEntryFilter(index, startDate) 38 | } 39 | throw IllegalArgumentException("Illegal filter type ${type}!") 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/model/FilterParameter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.model 19 | 20 | import java.util.* 21 | 22 | /** 23 | * Class that represents a filter parameter and its value. 24 | * @author zouroboros 25 | */ 26 | data class FilterParameter(val name: String, var stringValue: String?, var dateValue: Date?) {} -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/model/FilterType.kt: -------------------------------------------------------------------------------- 1 | package me.murks.feedwatcher.model 2 | 3 | /** 4 | * @author zouroboros 5 | * @date 8/13/18. 6 | */ 7 | enum class FilterType { 8 | CONTAINS, 9 | FEED, 10 | NEW 11 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/model/FilterTypeCallback.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.model 19 | 20 | /** 21 | * Interface that allows the implementation of the visitor pattern for 22 | * [me.murks.feedwatcher.model.Filter]. 23 | * @author zouroboros 24 | */ 25 | interface FilterTypeCallback { 26 | fun filter(filter: ContainsFilter): R 27 | fun filter(filter: FeedFilter): R 28 | fun filter(filter: NewEntryFilter): R 29 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/model/NewEntryFilter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 - 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.model 19 | 20 | import me.murks.feedwatcher.atomrss.FeedItem 21 | import java.util.* 22 | 23 | /** 24 | * Filter for new entries in a feed. Filters all entries after a certain date 25 | * @author zouroboros 26 | */ 27 | class NewEntryFilter(index: Int, val start: Date): Filter(FilterType.NEW, index) { 28 | 29 | companion object { 30 | const val startDateParameterName = "startDate" 31 | } 32 | 33 | override fun filterCallback(callback: FilterTypeCallback): R { 34 | return callback.filter(this) 35 | } 36 | 37 | override fun filterItems(feed: Feed, items: List): List { 38 | return items.filter { it.date?.after(start) == true } 39 | } 40 | 41 | override fun parameter(): List { 42 | return listOf(FilterParameter(startDateParameterName, null, start)) 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/model/Query.kt: -------------------------------------------------------------------------------- 1 | package me.murks.feedwatcher.model 2 | 3 | /** 4 | * @author zouroboros 5 | * @date 8/17/18. 6 | */ 7 | data class Query(val id: Long, val name: String, val filter: List) -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/model/Result.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 - 2022 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.model 19 | 20 | import me.murks.feedwatcher.atomrss.FeedItem 21 | import java.util.* 22 | 23 | /** 24 | * @author zouroboros 25 | */ 26 | data class Result(val id: Long, val feed: Feed, val queries: Collection, val item: FeedItem, 27 | val found: Date, val unread: Boolean) -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/model/Scan.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.model 19 | 20 | import java.util.* 21 | 22 | /** 23 | * Class represents a record about a scan of a feed. 24 | * @author zouroboros 25 | */ 26 | data class Scan(val feed: Feed, val sucessfully: Boolean, val error: String?, val scanDate: Date) 27 | -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/model/ScanInterval.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2021 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.model 19 | 20 | /** 21 | * Container representing a scan interval 22 | * @author zouroboros 23 | */ 24 | data class ScanInterval(val minutes: Int, val hours: Int) -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/tasks/ActionTask.kt: -------------------------------------------------------------------------------- 1 | package me.murks.feedwatcher.tasks 2 | 3 | import android.os.AsyncTask 4 | import me.murks.feedwatcher.Either 5 | import me.murks.feedwatcher.Left 6 | import me.murks.feedwatcher.Right 7 | 8 | /** 9 | * @author zouroboros 10 | */ 11 | class ActionTask(private val f: () -> TResult, 12 | listener: ErrorHandlingTaskListener) : 13 | AsyncTask>() { 14 | 15 | private val _listener 16 | = ErrorHandlingTaskListenerWrapper(listener) 17 | 18 | override fun doInBackground(vararg args: Void): Either { 19 | try { 20 | return Right(f()) 21 | } catch (e: Exception) { 22 | return Left(e) 23 | } 24 | } 25 | 26 | override fun onProgressUpdate(vararg values: TResult) { 27 | for (value in values) { 28 | _listener.onProgress(value) 29 | } 30 | super.onProgressUpdate(*values) 31 | } 32 | 33 | override fun onPostExecute(result: Either) { 34 | super.onPostExecute(result) 35 | _listener.onResult(result) 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/tasks/FeedsFilter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019 - 2022 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.tasks 19 | 20 | import android.util.Xml 21 | import me.murks.feedwatcher.Either 22 | import me.murks.feedwatcher.Left 23 | import me.murks.feedwatcher.Right 24 | import me.murks.feedwatcher.atomrss.FeedParser 25 | import me.murks.feedwatcher.model.Feed 26 | import me.murks.feedwatcher.model.Query 27 | import me.murks.feedwatcher.model.Result 28 | import okhttp3.OkHttpClient 29 | import okhttp3.Request 30 | import java.io.IOException 31 | import java.lang.Exception 32 | import java.util.* 33 | 34 | typealias FilterResult = Either, Pair>> 35 | 36 | /** 37 | * Object which contains method for filtering feeds 38 | * @author zouroboros 39 | */ 40 | object FeedsFilter { 41 | fun filterFeeds(feeds: Collection, queries: Collection): Sequence, Pair>>> { 42 | val client = OkHttpClient() 43 | return feeds.asSequence().map {feed -> 44 | try { 45 | val request = Request.Builder().url(feed.url).build() 46 | val response = client.newCall(request).execute() 47 | 48 | if (response.isSuccessful) { 49 | response.body!!.use { 50 | it.byteStream().use { 51 | stream -> 52 | val feedIo = FeedParser(stream, Xml.newPullParser(), Xml.newSerializer()) 53 | val items = feedIo.items(feed.lastUpdate?: Date(0)) 54 | // we only want items with a date. 55 | // this could be done more intelligently but for now we rely on the 56 | // feeds to provde a date. 57 | .filter { it.date != null } 58 | 59 | val matchingItems = queries.associateBy({query -> query}, 60 | { query -> 61 | query.filter.fold(items) { 62 | acc, filter -> filter.filterItems(feed, acc)}}) 63 | .entries.map { entry -> entry.value.map { 64 | item -> AbstractMap.SimpleEntry(entry.key, item) } } 65 | .flatten() 66 | .groupBy({ it.value }) { it.key } 67 | 68 | val results = matchingItems.entries.map { Result(0, feed, it.value, it.key, Date(), true) } 69 | return@map Right(Pair(feed, results)) 70 | } 71 | } 72 | } else { 73 | return@map Left(Pair(feed, IOException("${feed.url} returned status code ${response.code}."))) 74 | } 75 | 76 | } catch (e: Exception) { 77 | return@map Left(Pair(feed, e)) 78 | } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/tasks/FilterFeedsJob.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2019-2020 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.tasks 19 | 20 | import android.app.job.JobParameters 21 | import android.app.job.JobService 22 | import me.murks.feedwatcher.AndroidEnvironment 23 | import me.murks.feedwatcher.FeedWatcherApp 24 | import java.util.concurrent.CompletableFuture 25 | 26 | /** 27 | * @author zouroboros 28 | */ 29 | class FilterFeedsJob(): JobService() { 30 | 31 | private lateinit var app: FeedWatcherApp 32 | private lateinit var parameter: JobParameters 33 | private lateinit var future: CompletableFuture 34 | 35 | override fun onStartJob(p0: JobParameters): Boolean { 36 | parameter = p0 37 | app = FeedWatcherApp(AndroidEnvironment(this)) 38 | app.environment.log.info("Starting ${FilterFeedsJob::class.qualifiedName}.") 39 | future = Tasks.filterFeeds(app) 40 | future.thenAccept { jobFinished(p0, false) } 41 | app.environment.log.info("${FilterFeedsJob::class.qualifiedName} started.") 42 | return true // job may still be running 43 | } 44 | 45 | override fun onStopJob(p0: JobParameters?): Boolean { 46 | app.environment.log.info("${FilterFeedsJob::class.qualifiedName} canceled.") 47 | future.cancel(true) 48 | return false // no rescheduling 49 | } 50 | 51 | override fun onDestroy() { 52 | super.onDestroy() 53 | if(::app.isInitialized) { 54 | app.environment.close() 55 | } 56 | } 57 | 58 | companion object { 59 | const val NOTIFICATION_ID = 1 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/tasks/StreamingTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of FeedWatcher. 3 | 4 | FeedWatcher is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | FeedWatcher is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with FeedWatcher. If not, see . 16 | Copyright 2020 Zouroboros 17 | */ 18 | package me.murks.feedwatcher.tasks 19 | 20 | import android.os.AsyncTask 21 | import me.murks.feedwatcher.Either 22 | import me.murks.feedwatcher.Left 23 | import me.murks.feedwatcher.Right 24 | import java.lang.Exception 25 | 26 | /** 27 | * Task for processing lists. 28 | * 29 | * @author zouroboros 30 | * 31 | * @param func The function that processes a list item. 32 | * @param listener The listener that processes the result. 33 | */ 34 | class StreamingTask(private val func: (input: TInput) -> TOutput, 35 | private val listener: Listener) : 36 | AsyncTask, TOutput>, Unit>() { 37 | 38 | /** 39 | * Class that hold an error and the item that was processed when the error occured. 40 | */ 41 | class Error(val item: TInput, val error: Exception) 42 | 43 | /*** 44 | * Interface for listener that process the results. 45 | */ 46 | interface Listener { 47 | fun onResult(item: TOutput) 48 | fun onError(item: Error) 49 | fun onFinished() 50 | } 51 | 52 | override fun doInBackground(vararg inputs: TInput) { 53 | inputs.forEach { 54 | try { 55 | publishProgress(Right(func(it))) 56 | } catch (e: Exception) { 57 | publishProgress(Left(Error(it, e))) 58 | } 59 | } 60 | } 61 | 62 | override fun onProgressUpdate(vararg values: Either, TOutput>) { 63 | for (value in values) { 64 | when(value) { 65 | is Left -> listener.onError(value.value) 66 | is Right -> listener.onResult(value.value) 67 | } 68 | } 69 | super.onProgressUpdate(*values) 70 | } 71 | 72 | override fun onPostExecute(result: Unit?) { 73 | listener.onFinished() 74 | super.onPostExecute(result) 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/java/me/murks/feedwatcher/tasks/TaskListener.kt: -------------------------------------------------------------------------------- 1 | package me.murks.feedwatcher.tasks 2 | 3 | import me.murks.feedwatcher.Either 4 | import me.murks.feedwatcher.Left 5 | import me.murks.feedwatcher.Right 6 | 7 | /** 8 | * @author zouroboros 9 | */ 10 | interface TaskListener { 11 | fun onProgress(progress: TP) 12 | fun onResult(result: TR) 13 | } 14 | 15 | interface ErrorHandlingTaskListener { 16 | fun onSuccessResult(result: TR) 17 | fun onErrorResult(error: TE) 18 | fun onProgress(progress: TP) 19 | } 20 | 21 | class ErrorHandlingTaskListenerWrapper( 22 | val listener: ErrorHandlingTaskListener) : TaskListener> { 23 | 24 | override fun onProgress(progress: TP) { 25 | listener.onProgress(progress) 26 | } 27 | 28 | override fun onResult(result: Either) { 29 | if(result.isLeft()) { 30 | listener.onErrorResult((result as Left).value) 31 | } else { 32 | listener.onSuccessResult((result as Right).value) 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_feed_icon.xml: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_feedwatcher_notification.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_menu.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_feed.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 30 | 31 | 40 | 41 | 50 | 51 | 60 | 61 | 69 | 70 | 79 | 80 |