forked from Louvorg/ReaderForSelfoss-multiplatform
Compare commits
73 Commits
drone
...
8c817b5938
Author | SHA1 | Date | |
---|---|---|---|
8c817b5938 | |||
4857a3d0ac | |||
fbcb428e96 | |||
e281751bb0 | |||
0eed9a8d07 | |||
9603860bae | |||
75b566a38d | |||
fb572dbb27 | |||
34028949d7 | |||
44a0469b17 | |||
c87473e8f1 | |||
de43abf019 | |||
e60f3a9d91 | |||
255fbcb12f | |||
0caeb94e64 | |||
6f6a42b878 | |||
67d54f0dd7 | |||
437aa0abec | |||
216c639a23 | |||
7258452625 | |||
d0d82751e2 | |||
3b8f4991e9 | |||
2547ce824a | |||
59eb399cfa | |||
495b101355 | |||
afcc55e907 | |||
0c570efc47 | |||
a23a4cea0e | |||
78cb5d047f | |||
a9caaefb4d | |||
4c78b22614 | |||
1d5ab3205e | |||
df4903cae5 | |||
2a78be69b5 | |||
8c69bb8c3c | |||
9203012a97 | |||
2a44162c5a | |||
20588aab81 | |||
0c8e49214f | |||
97d5063339 | |||
7c37b183d7 | |||
82c4a5a1f9 | |||
47b7062e16 | |||
b9497ca939 | |||
1258ed3ad3 | |||
d838f509d4 | |||
3c5b606a02 | |||
d1481a1db6 | |||
d654b1b0bd | |||
f56861a3c2 | |||
492e7e4aed | |||
551a3e3caa | |||
c224b8a0b3 | |||
13ea7a693b | |||
0f3c48dd8e | |||
d4c2373bac | |||
4f32097821 | |||
37fa4a1a8e | |||
112194dd4f | |||
72d9ef92d2 | |||
1392e2a571 | |||
e9cb3d2f37 | |||
dec620a409 | |||
4d29ee0b92 | |||
33333ca998 | |||
8d87eef0fc | |||
5a26513ed7 | |||
5b7f5225d8 | |||
03f53bf9c9 | |||
e06e6d580d | |||
63e8649512 | |||
6260c3fc06 | |||
77917dd940 |
13
.drone.yml
13
.drone.yml
@ -1,20 +1,21 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: android
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: mingc/android-build-box:latest
|
||||
commands:
|
||||
- ./gradlew build
|
||||
|
||||
- name: code-analysis
|
||||
image: mingc/android-build-box:latest
|
||||
failure: ignore
|
||||
commands:
|
||||
- ls -la
|
||||
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.sources=. -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN
|
||||
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\""
|
||||
environment:
|
||||
SONAR_HOST_URL:
|
||||
from_secret: sonarScannerHostUrl
|
||||
SONAR_LOGIN:
|
||||
from_secret: sonarScannerLogin
|
||||
|
||||
- name: build
|
||||
image: mingc/android-build-box:latest
|
||||
commands:
|
||||
- ./gradlew :androidApp:build -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
|
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
@ -10,7 +10,7 @@ Please read the guidelines before contributing, and follow them (or try to) when
|
||||
|
||||
There are many ways to contribute to this project, you could [translate the app](https://crowdin.com/project/readerforselfoss), report bugs, request missing features, suggest enhancements and changes to existing ones. You also can improve the README with useful tips that could help the other users.
|
||||
|
||||
You can fork the repository, and [help me solve some issues](https://github.com/aminecmi/ReaderforSelfoss/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://github.com/aminecmi/ReaderforSelfoss/issues)
|
||||
You can fork the repository, and [help me solve some issues](https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues)
|
||||
|
||||
### What I can't help you with.
|
||||
|
||||
@ -28,7 +28,7 @@ Always check if the web version of your instance is working.
|
||||
|
||||
### Pull requests
|
||||
|
||||
* Don't create a PR for translations. See [here](https://github.com/aminecmi/ReaderforSelfoss/pull/170#issuecomment-355715654) for an explanation why.
|
||||
* Don't create a PR for translations.
|
||||
* Please ask before starting to work on an issue. I may be working on it, or someone else could be doing so.
|
||||
* Each pull request should implement **ONE** feature or bugfix. Keep in mind that you can submit as many PR as you want.
|
||||
* Your code must be simple and clear enough to avoid using comments to explain what it does.
|
||||
|
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -5,7 +5,7 @@
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I have added tests to cover my changes.
|
||||
- [ ] All new and existing tests passed.
|
||||
- [ ] This is **NOT** translation related. (See [here](https://github.com/aminecmi/ReaderforSelfoss/pull/170#issuecomment-355715654))
|
||||
- [ ] This is **NOT** translation related.
|
||||
|
||||
This closes issue #XXX
|
||||
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -319,3 +319,5 @@ fabric.properties
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/gradle,kotlin,androidstudio,android,xcode,swift
|
||||
|
||||
|
||||
crowdin.properties
|
10
README.md
10
README.md
@ -22,15 +22,15 @@ If you are a user, you can still create new issues. I'll fix them when I can.
|
||||
|
||||
1. **You'll have to have a Selfoss instance running.** You'll find everything you need to install it [here](https://selfoss.aditu.de/).
|
||||
|
||||
2. Check the [Contribution guide](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/blob/master/.github/CONTRIBUTING.md).
|
||||
2. Check the [Contribution guide](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md).
|
||||
|
||||
3. Build the project by following [these steps](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/blob/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
|
||||
3. Build the project by following [these steps](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
|
||||
|
||||
## Useful links
|
||||
|
||||
- [Check what changed](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/blob/master/CHANGELOG.md)
|
||||
- [See what I'm doing](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/projects/1)
|
||||
- [Create an issue, or request a new feature](https://github.com/aminecmi/ReaderforSelfoss-multiplatform/issues)
|
||||
- [Check what changed](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/CHANGELOG.md)
|
||||
- [See what I'm doing](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/projects/1)
|
||||
- [Create an issue, or request a new feature](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/issues)
|
||||
- [Help translation the app](https://crowdin.com/project/readerforselfoss)
|
||||
|
||||
## Contributors (V1) (Alphabetical order) ❤️
|
||||
|
@ -1,5 +1,7 @@
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
val ignoreGitVersion: String by project
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
@ -32,11 +34,19 @@ fun gitVersion(): String {
|
||||
}
|
||||
|
||||
fun versionCodeFromGit(): Int {
|
||||
if (ignoreGitVersion == "true") {
|
||||
// don't care
|
||||
return 1
|
||||
}
|
||||
println("version code " + gitVersion())
|
||||
return gitVersion().toInt()
|
||||
}
|
||||
|
||||
fun versionNameFromGit(): String {
|
||||
if (ignoreGitVersion == "true") {
|
||||
// don't care
|
||||
return "1"
|
||||
}
|
||||
println("version name " + gitVersion())
|
||||
return gitVersion()
|
||||
}
|
||||
@ -68,12 +78,6 @@ android {
|
||||
|
||||
// tests
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments += mapOf("room.schemaLocation" to "$projectDir/schemas")
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
@ -171,22 +175,29 @@ dependencies {
|
||||
implementation("androidx.viewpager2:viewpager2:1.1.0-beta01")
|
||||
|
||||
//Dependency Injection
|
||||
implementation("org.kodein.di:kodein-di:7.12.0")
|
||||
implementation("org.kodein.di:kodein-di-framework-android-x:7.12.0")
|
||||
implementation("org.kodein.di:kodein-di:7.14.0")
|
||||
implementation("org.kodein.di:kodein-di-framework-android-x:7.14.0")
|
||||
implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.14.0")
|
||||
|
||||
//Settings
|
||||
implementation("com.russhwolf:multiplatform-settings-no-arg:0.9")
|
||||
|
||||
//Logging
|
||||
implementation("io.github.aakira:napier:2.6.1")
|
||||
|
||||
//PhotoView
|
||||
implementation("com.github.chrisbanes:PhotoView:2.3.0")
|
||||
|
||||
implementation("androidx.core:core-ktx:1.7.0")
|
||||
implementation("androidx.core:core-ktx:1.8.0")
|
||||
|
||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0")
|
||||
implementation("androidx.lifecycle:lifecycle-common-java8:2.4.0")
|
||||
// implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
|
||||
// implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1")
|
||||
// implementation("androidx.lifecycle:lifecycle-runtime:2.5.1")
|
||||
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
|
||||
|
||||
implementation("androidx.room:room-ktx:2.4.0-beta01")
|
||||
kapt("androidx.room:room-compiler:2.4.0-beta01")
|
||||
// Network information
|
||||
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
|
||||
|
||||
implementation("android.arch.work:work-runtime-ktx:1.0.1")
|
||||
// SQLDELIGHT
|
||||
implementation("com.squareup.sqldelight:android-driver:1.5.3")
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "08ca537d7ac9d4dd216e8e395d70801a",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "tags",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "tag",
|
||||
"columnName": "tag",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "color",
|
||||
"columnName": "color",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"tag"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "sources",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "spout",
|
||||
"columnName": "spout",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"08ca537d7ac9d4dd216e8e395d70801a\")"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 2,
|
||||
"identityHash": "6fa6944b04100d68eab61039876a8804",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "tags",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "tag",
|
||||
"columnName": "tag",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "color",
|
||||
"columnName": "color",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"tag"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "sources",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "spout",
|
||||
"columnName": "spout",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "items",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "datetime",
|
||||
"columnName": "datetime",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbnail",
|
||||
"columnName": "thumbnail",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "link",
|
||||
"columnName": "link",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sourcetitle",
|
||||
"columnName": "sourcetitle",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"6fa6944b04100d68eab61039876a8804\")"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,226 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 3,
|
||||
"identityHash": "7ad9c4961992c13b670128485ebb3efc",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "tags",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "tag",
|
||||
"columnName": "tag",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "color",
|
||||
"columnName": "color",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"tag"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "sources",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "spout",
|
||||
"columnName": "spout",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "items",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "datetime",
|
||||
"columnName": "datetime",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbnail",
|
||||
"columnName": "thumbnail",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "link",
|
||||
"columnName": "link",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sourcetitle",
|
||||
"columnName": "sourcetitle",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "actions",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "articleId",
|
||||
"columnName": "articleid",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "read",
|
||||
"columnName": "read",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unstarred",
|
||||
"columnName": "unstarred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"7ad9c4961992c13b670128485ebb3efc\")"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,226 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 4,
|
||||
"identityHash": "9cf8b03d32f80dfd58160599a1df197d",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "tags",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "tag",
|
||||
"columnName": "tag",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "color",
|
||||
"columnName": "color",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"tag"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "sources",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "spout",
|
||||
"columnName": "spout",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "items",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT, `icon` TEXT, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "datetime",
|
||||
"columnName": "datetime",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbnail",
|
||||
"columnName": "thumbnail",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "link",
|
||||
"columnName": "link",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sourcetitle",
|
||||
"columnName": "sourcetitle",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "actions",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "articleId",
|
||||
"columnName": "articleid",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "read",
|
||||
"columnName": "read",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unstarred",
|
||||
"columnName": "unstarred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"9cf8b03d32f80dfd58160599a1df197d\")"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,226 +0,0 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 4,
|
||||
"identityHash": "9cf8b03d32f80dfd58160599a1df197d",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "tags",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `color` TEXT NOT NULL, `unread` INTEGER NOT NULL, PRIMARY KEY(`tag`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "tag",
|
||||
"columnName": "tag",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "color",
|
||||
"columnName": "color",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"tag"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "sources",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `tags` TEXT NOT NULL, `spout` TEXT NOT NULL, `error` TEXT NOT NULL, `icon` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "spout",
|
||||
"columnName": "spout",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "error",
|
||||
"columnName": "error",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "items",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT, `icon` TEXT, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "datetime",
|
||||
"columnName": "datetime",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "content",
|
||||
"columnName": "content",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "thumbnail",
|
||||
"columnName": "thumbnail",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "icon",
|
||||
"columnName": "icon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "link",
|
||||
"columnName": "link",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sourcetitle",
|
||||
"columnName": "sourcetitle",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": false
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "actions",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "articleId",
|
||||
"columnName": "articleid",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "read",
|
||||
"columnName": "read",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unread",
|
||||
"columnName": "unread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "starred",
|
||||
"columnName": "starred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unstarred",
|
||||
"columnName": "unstarred",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"columnNames": [
|
||||
"id"
|
||||
],
|
||||
"autoGenerate": true
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9cf8b03d32f80dfd58160599a1df197d')"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
// TODO
|
||||
//package bou.amine.apps.readerforselfossv2.android.utils
|
||||
//
|
||||
//import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
//import bou.amine.apps.readerforselfossv2.android.utils.parseDate
|
||||
//import org.junit.Test
|
||||
//
|
||||
//class DateUtilsTest {
|
||||
//
|
||||
// @Test
|
||||
// fun parseDateV4() {
|
||||
//
|
||||
// Config.apiVersion = 4
|
||||
// val dateString = "2013-04-07T13:43:00+01:00"
|
||||
//
|
||||
// val milliseconds = parseDate(dateString).toEpochMilli()
|
||||
// val correctMilliseconds : Long = 1365338580000
|
||||
//
|
||||
// assert(milliseconds == correctMilliseconds)
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// fun parseDateV1() {
|
||||
// Config.apiVersion = 0
|
||||
// val dateString = "2013-04-07 13:43:00"
|
||||
//
|
||||
// val milliseconds = parseDate(dateString).toEpochMilli()
|
||||
// val correctMilliseconds = 1365342180000
|
||||
//
|
||||
// assert(milliseconds == correctMilliseconds)
|
||||
// }
|
||||
//}
|
@ -11,6 +11,7 @@ import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
|
||||
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import com.ftinc.scoop.Scoop
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -109,33 +110,40 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
|
||||
}
|
||||
|
||||
|
||||
fun handleSpoutFailure(networkIssue: Boolean = false) {
|
||||
Toast.makeText(
|
||||
this@AddSourceActivity,
|
||||
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
mProgress.visibility = View.GONE
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val items = repository.getSpouts()
|
||||
if (items != null) {
|
||||
try {
|
||||
val items = repository.getSpouts()
|
||||
if (items != null) {
|
||||
val itemsStrings = items.map { it.value.name }
|
||||
for ((key, value) in items) {
|
||||
spoutsKV[value.name] = key
|
||||
}
|
||||
|
||||
val itemsStrings = items.map { it.value.name }
|
||||
for ((key, value) in items) {
|
||||
spoutsKV[value.name] = key
|
||||
mProgress.visibility = View.GONE
|
||||
formContainer.visibility = View.VISIBLE
|
||||
|
||||
val spinnerArrayAdapter =
|
||||
ArrayAdapter(
|
||||
this@AddSourceActivity,
|
||||
android.R.layout.simple_spinner_item,
|
||||
itemsStrings
|
||||
)
|
||||
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
spoutsSpinner.adapter = spinnerArrayAdapter
|
||||
} else {
|
||||
handleSpoutFailure()
|
||||
}
|
||||
|
||||
mProgress.visibility = View.GONE
|
||||
formContainer.visibility = View.VISIBLE
|
||||
|
||||
val spinnerArrayAdapter =
|
||||
ArrayAdapter(
|
||||
this@AddSourceActivity,
|
||||
android.R.layout.simple_spinner_item,
|
||||
itemsStrings
|
||||
)
|
||||
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
spoutsSpinner.adapter = spinnerArrayAdapter
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this@AddSourceActivity,
|
||||
R.string.cant_get_spouts,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
mProgress.visibility = View.GONE
|
||||
} catch (e: NetworkUnavailableException) {
|
||||
handleSpoutFailure(networkIssue = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.*
|
||||
import androidx.room.Room
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
@ -28,15 +28,6 @@ import bou.amine.apps.readerforselfossv2.android.adapters.ItemListAdapter
|
||||
import bou.amine.apps.readerforselfossv2.android.adapters.ItemsAdapter
|
||||
import bou.amine.apps.readerforselfossv2.android.background.LoadingWorker
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityHomeBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.model.getIcon
|
||||
import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabase
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabaseService
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
||||
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
||||
@ -44,12 +35,12 @@ import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toEntity
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toView
|
||||
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||
import bou.amine.apps.readerforselfossv2.utils.longHash
|
||||
import com.ashokvarma.bottomnavigation.BottomNavigationBar
|
||||
import com.ashokvarma.bottomnavigation.BottomNavigationItem
|
||||
@ -79,12 +70,9 @@ import org.kodein.di.DIAware
|
||||
import org.kodein.di.android.closestDI
|
||||
import org.kodein.di.instance
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware {
|
||||
|
||||
private lateinit var dataBase: AndroidDeviceDatabase
|
||||
private lateinit var dbService: AndroidDeviceDatabaseService
|
||||
private val MENU_PREFERENCES = 12302
|
||||
private val DRAWER_ID_TAGS = 100101L
|
||||
private val DRAWER_ID_HIDDEN_TAGS = 101100L
|
||||
@ -126,16 +114,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
private var recyclerAdapter: RecyclerView.Adapter<*>? = null
|
||||
|
||||
private var fromTabShortcut: Boolean = false
|
||||
private var offlineShortcut: Boolean = false
|
||||
|
||||
private lateinit var tagsBadge: Map<Long, Int>
|
||||
|
||||
private lateinit var db: AppDatabase
|
||||
|
||||
private lateinit var config: Config
|
||||
|
||||
override val di by closestDI()
|
||||
private val repository : Repository by instance()
|
||||
private val viewModel: AppViewModel by instance()
|
||||
|
||||
data class DrawerData(val tags: List<SelfossModel.Tag>?, val sources: List<SelfossModel.Source>?)
|
||||
|
||||
@ -153,13 +139,18 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
val view = binding.root
|
||||
|
||||
fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1
|
||||
offlineShortcut = intent.getBooleanExtra("startOffline", false)
|
||||
repository.offlineOverride = intent.getBooleanExtra("startOffline", false)
|
||||
|
||||
if (fromTabShortcut) {
|
||||
elementsShown = ItemType.fromInt(intent.getIntExtra("shortcutTab", ItemType.UNREAD.position))
|
||||
}
|
||||
|
||||
setContentView(view)
|
||||
lifecycleScope.launch {
|
||||
viewModel.refreshingIndicatorProvider.collect { showRefresh ->
|
||||
binding.swipeRefreshLayout.isRefreshing = showRefresh
|
||||
}
|
||||
}
|
||||
|
||||
handleThemeBinding()
|
||||
|
||||
@ -170,24 +161,29 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
binding.drawerContainer.addDrawerListener(mDrawerToggle)
|
||||
mDrawerToggle.syncState()
|
||||
|
||||
db = Room.databaseBuilder(
|
||||
applicationContext,
|
||||
AppDatabase::class.java, "selfoss-database"
|
||||
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
|
||||
|
||||
|
||||
customTabActivityHelper = CustomTabActivityHelper()
|
||||
handleSettings()
|
||||
|
||||
dataBase = AndroidDeviceDatabase(applicationContext)
|
||||
lifecycleScope.launch {
|
||||
viewModel.items.collect { fetchedItems ->
|
||||
items = fetchedItems
|
||||
handleListResult()
|
||||
}
|
||||
}
|
||||
|
||||
handleBottomBar()
|
||||
handleDrawer()
|
||||
|
||||
handleSwipeRefreshLayout()
|
||||
|
||||
handleSettings()
|
||||
|
||||
getElementsAccordingToTab()
|
||||
|
||||
handleBadgesContent()
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.tryToCacheItemsAndGetNewOnes()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun handleSwipeRefreshLayout() {
|
||||
@ -197,13 +193,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
R.color.refresh_progress_3
|
||||
)
|
||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||
offlineShortcut = false
|
||||
repository.offlineOverride = false
|
||||
lastFetchDone = false
|
||||
handleDrawerItems()
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
getElementsAccordingToTab()
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
}
|
||||
viewModel.getItems(false, elementsShown)
|
||||
}
|
||||
|
||||
val simpleItemTouchCallback =
|
||||
@ -239,8 +232,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
adapter.handleItemAtIndex(position)
|
||||
|
||||
reloadBadgeContent()
|
||||
|
||||
val tagHashes = i.tags.map { it.longHash() }
|
||||
tagsBadge = tagsBadge.map {
|
||||
if (tagHashes.contains(it.key)) {
|
||||
@ -272,16 +263,23 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
tabNewBadge = TextBadgeItem()
|
||||
.setText("")
|
||||
.setHideOnSelect(false).hide(false)
|
||||
.setHideOnSelect(false)
|
||||
.setBackgroundColor(appColors.colorPrimary)
|
||||
if (!displayUnreadCount) {
|
||||
tabNewBadge.hide(false)
|
||||
}
|
||||
tabArchiveBadge = TextBadgeItem()
|
||||
.setText("")
|
||||
.setHideOnSelect(false).hide(false)
|
||||
.setHideOnSelect(false)
|
||||
.setBackgroundColor(appColors.colorPrimary)
|
||||
tabStarredBadge = TextBadgeItem()
|
||||
.setText("")
|
||||
.setHideOnSelect(false).hide(false)
|
||||
.setHideOnSelect(false)
|
||||
.setBackgroundColor(appColors.colorPrimary)
|
||||
if (!displayAllCount) {
|
||||
tabArchiveBadge.hide(false)
|
||||
tabStarredBadge.hide(false)
|
||||
}
|
||||
|
||||
val tabNew =
|
||||
BottomNavigationItem(
|
||||
@ -338,7 +336,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
handleRecurringTask()
|
||||
|
||||
handleOfflineActions()
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
repository.handleDBActions()
|
||||
}
|
||||
|
||||
getElementsAccordingToTab()
|
||||
}
|
||||
@ -408,6 +408,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
val drawerListener = object : DrawerLayout.DrawerListener {
|
||||
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
|
||||
// We do nothing
|
||||
}
|
||||
|
||||
override fun onDrawerOpened(drawerView: View) {
|
||||
@ -419,6 +420,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
|
||||
override fun onDrawerStateChanged(newState: Int) {
|
||||
// We do nothing
|
||||
}
|
||||
|
||||
}
|
||||
@ -466,54 +468,61 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: refactor this.
|
||||
private fun handleDrawerItems() {
|
||||
tagsBadge = emptyMap()
|
||||
fun handleDrawerData(maybeDrawerData: DrawerData?, loadedFromCache: Boolean = false) {
|
||||
fun handleDrawerData(maybeDrawerData: DrawerData?) {
|
||||
fun createDrawerItem(
|
||||
it: SelfossModel.Tag
|
||||
) {
|
||||
val gd = GradientDrawable()
|
||||
val gdColor = try {
|
||||
Color.parseColor(it.color)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
appColors.colorPrimary
|
||||
}
|
||||
|
||||
gd.setColor(gdColor)
|
||||
gd.shape = GradientDrawable.RECTANGLE
|
||||
gd.setSize(30, 30)
|
||||
gd.cornerRadius = 30F
|
||||
|
||||
val drawerItem = PrimaryDrawerItem()
|
||||
.apply {
|
||||
nameText = it.tag.getHtmlDecoded()
|
||||
identifier = it.tag.longHash()
|
||||
iconDrawable = gd
|
||||
badgeStyle = BadgeStyle().apply {
|
||||
textColor = ColorHolder.fromColor(Color.WHITE)
|
||||
color = ColorHolder.fromColor(appColors.colorAccent)
|
||||
}
|
||||
onDrawerItemClickListener = { _, _, _ ->
|
||||
repository.tagFilter = it
|
||||
repository.sourceFilter = null
|
||||
getElementsAccordingToTab()
|
||||
fetchOnEmptyList()
|
||||
false
|
||||
}
|
||||
}
|
||||
if (it.unread > 0) {
|
||||
drawerItem.badgeText = it.unread.toString()
|
||||
}
|
||||
|
||||
binding.mainDrawer.itemAdapter.add(drawerItem)
|
||||
}
|
||||
|
||||
fun handleTags(maybeTags: List<SelfossModel.Tag>?) {
|
||||
if (maybeTags == null) {
|
||||
if (loadedFromCache) {
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
SecondaryDrawerItem()
|
||||
.apply { nameRes = R.string.drawer_error_loading_tags; isSelectable = false }
|
||||
)
|
||||
}
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
SecondaryDrawerItem()
|
||||
.apply { nameRes = R.string.drawer_error_loading_tags; isSelectable = false }
|
||||
)
|
||||
} else {
|
||||
val filteredTags = maybeTags
|
||||
.filterNot { hiddenTags.contains(it.tag) }
|
||||
.sortedBy { it.unread == 0 }
|
||||
tagsBadge = filteredTags.map {
|
||||
val gd = GradientDrawable()
|
||||
val gdColor = try {
|
||||
Color.parseColor(it.color)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
appColors.colorPrimary
|
||||
}
|
||||
|
||||
gd.setColor(gdColor)
|
||||
gd.shape = GradientDrawable.RECTANGLE
|
||||
gd.setSize(30, 30)
|
||||
gd.cornerRadius = 30F
|
||||
val drawerItem =
|
||||
PrimaryDrawerItem()
|
||||
.apply {
|
||||
nameText = it.getTitleDecoded()
|
||||
identifier = it.tag.longHash()
|
||||
iconDrawable = gd
|
||||
badgeStyle = BadgeStyle().apply {
|
||||
textColor = ColorHolder.fromColor(Color.WHITE)
|
||||
color = ColorHolder.fromColor(appColors.colorAccent) }
|
||||
onDrawerItemClickListener = { _,_,_ ->
|
||||
repository.tagFilter = it
|
||||
repository.sourceFilter = null
|
||||
getElementsAccordingToTab()
|
||||
fetchOnEmptyList()
|
||||
false
|
||||
} }
|
||||
if (it.unread > 0) {
|
||||
drawerItem.badgeText = it.unread.toString()
|
||||
}
|
||||
|
||||
binding.mainDrawer.itemAdapter.add(drawerItem)
|
||||
createDrawerItem(it)
|
||||
|
||||
(it.tag.longHash() to it.unread)
|
||||
}.toMap()
|
||||
@ -522,49 +531,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
fun handleHiddenTags(maybeTags: List<SelfossModel.Tag>?) {
|
||||
if (maybeTags == null) {
|
||||
if (loadedFromCache) {
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
SecondaryDrawerItem().apply {
|
||||
nameRes = R.string.drawer_error_loading_tags
|
||||
isSelectable = false
|
||||
}
|
||||
)
|
||||
}
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
SecondaryDrawerItem().apply {
|
||||
nameRes = R.string.drawer_error_loading_tags
|
||||
isSelectable = false
|
||||
}
|
||||
)
|
||||
} else {
|
||||
val filteredHiddenTags: List<SelfossModel.Tag> =
|
||||
maybeTags.filter { hiddenTags.contains(it.tag) }
|
||||
tagsBadge = filteredHiddenTags.map {
|
||||
val gd = GradientDrawable()
|
||||
val gdColor = try {
|
||||
Color.parseColor(it.color)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
appColors.colorPrimary
|
||||
}
|
||||
|
||||
gd.setColor(gdColor)
|
||||
gd.shape = GradientDrawable.RECTANGLE
|
||||
gd.setSize(30, 30)
|
||||
gd.cornerRadius = 30F
|
||||
val drawerItem =
|
||||
PrimaryDrawerItem().apply {
|
||||
nameText = it.getTitleDecoded()
|
||||
identifier = it.tag.longHash()
|
||||
iconDrawable = gd
|
||||
badgeStyle = BadgeStyle().apply {
|
||||
textColor = ColorHolder.fromColor(Color.WHITE)
|
||||
color = ColorHolder.fromColor(appColors.colorAccent) }
|
||||
onDrawerItemClickListener = { _,_,_ ->
|
||||
repository.tagFilter = it
|
||||
repository.sourceFilter = null
|
||||
getElementsAccordingToTab()
|
||||
fetchOnEmptyList()
|
||||
false
|
||||
} }
|
||||
|
||||
if (it.unread > 0) {
|
||||
drawerItem.badgeText = it.unread.toString()
|
||||
}
|
||||
binding.mainDrawer.itemAdapter.add(drawerItem)
|
||||
createDrawerItem(it)
|
||||
|
||||
(it.tag.longHash() to it.unread)
|
||||
}.toMap()
|
||||
@ -573,18 +550,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
fun handleSources(maybeSources: List<SelfossModel.Source>?) {
|
||||
if (maybeSources == null) {
|
||||
if (loadedFromCache) {
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
SecondaryDrawerItem().apply {
|
||||
nameRes = R.string.drawer_error_loading_sources
|
||||
isSelectable = false
|
||||
}
|
||||
)
|
||||
}
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
SecondaryDrawerItem().apply {
|
||||
nameRes = R.string.drawer_error_loading_sources
|
||||
isSelectable = false
|
||||
}
|
||||
)
|
||||
} else {
|
||||
for (source in maybeSources) {
|
||||
val item = PrimaryDrawerItem().apply {
|
||||
nameText = source.getTitleDecoded()
|
||||
nameText = source.title.getHtmlDecoded()
|
||||
identifier = source.id.toLong()
|
||||
iconUrl = source.getIcon(repository.baseUrl)
|
||||
onDrawerItemClickListener = { _,_,_ ->
|
||||
@ -669,70 +644,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
)
|
||||
|
||||
if (!loadedFromCache) {
|
||||
if (maybeDrawerData.tags != null) {
|
||||
thread {
|
||||
val tagEntities = maybeDrawerData.tags.map { it.toEntity() }
|
||||
db.drawerDataDao().deleteAllTags()
|
||||
db.drawerDataDao().insertAllTags(*tagEntities.toTypedArray())
|
||||
}
|
||||
}
|
||||
if (maybeDrawerData.sources != null) {
|
||||
thread {
|
||||
val sourceEntities =
|
||||
maybeDrawerData.sources.map { it.toEntity() }
|
||||
db.drawerDataDao().deleteAllSources()
|
||||
db.drawerDataDao().insertAllSources(*sourceEntities.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!loadedFromCache) {
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
PrimaryDrawerItem().apply {
|
||||
nameRes = R.string.no_tags_loaded
|
||||
identifier = DRAWER_ID_TAGS
|
||||
isSelectable = false
|
||||
},
|
||||
PrimaryDrawerItem().apply {
|
||||
nameRes = R.string.no_sources_loaded
|
||||
identifier = DRAWER_ID_SOURCES
|
||||
isSelectable = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun drawerApiCalls(maybeDrawerData: DrawerData?) {
|
||||
var tags: List<SelfossModel.Tag>? = null
|
||||
var sources: List<SelfossModel.Source>?
|
||||
|
||||
fun sourcesApiCall() {
|
||||
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut) && updateSources) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val response = repository.getSources()
|
||||
if (response != null) {
|
||||
sources = response
|
||||
val apiDrawerData = DrawerData(tags, sources)
|
||||
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) {
|
||||
handleDrawerData(apiDrawerData)
|
||||
}
|
||||
} else {
|
||||
val apiDrawerData = DrawerData(tags, null)
|
||||
if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) {
|
||||
handleDrawerData(apiDrawerData)
|
||||
}
|
||||
}
|
||||
binding.mainDrawer.itemAdapter.add(
|
||||
PrimaryDrawerItem().apply {
|
||||
nameRes = R.string.no_tags_loaded
|
||||
identifier = DRAWER_ID_TAGS
|
||||
isSelectable = false
|
||||
},
|
||||
PrimaryDrawerItem().apply {
|
||||
nameRes = R.string.no_sources_loaded
|
||||
identifier = DRAWER_ID_SOURCES
|
||||
isSelectable = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut) && updateSources) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
tags = repository.getTags()
|
||||
sourcesApiCall()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -743,12 +667,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
)
|
||||
|
||||
thread {
|
||||
val drawerData = DrawerData(db.drawerDataDao().tags().map { it.toView() },
|
||||
db.drawerDataDao().sources().map { it.toView() })
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val drawerData = DrawerData(repository.getTags(),
|
||||
repository.getSources())
|
||||
runOnUiThread {
|
||||
handleDrawerData(drawerData, loadedFromCache = true)
|
||||
drawerApiCalls(drawerData)
|
||||
handleDrawerData(drawerData)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -885,21 +808,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
}
|
||||
firstVisible = if (appendResults) firstVisible else 0
|
||||
|
||||
getItems(appendResults, elementsShown)
|
||||
}
|
||||
|
||||
private fun getItems(appendResults: Boolean, itemType: ItemType) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
binding.swipeRefreshLayout.isRefreshing = true
|
||||
repository.displayedItems = itemType
|
||||
items = if (appendResults) {
|
||||
repository.getOlderItems()
|
||||
} else {
|
||||
repository.getNewerItems()
|
||||
}
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
handleListResult()
|
||||
}
|
||||
viewModel.getItems(appendResults, elementsShown)
|
||||
}
|
||||
|
||||
private fun handleListResult(appendResults: Boolean = false) {
|
||||
@ -920,7 +829,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
ItemCardAdapter(
|
||||
this,
|
||||
items,
|
||||
db,
|
||||
customTabActivityHelper,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
@ -935,7 +843,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
ItemListAdapter(
|
||||
this,
|
||||
items,
|
||||
db,
|
||||
customTabActivityHelper,
|
||||
internalBrowser,
|
||||
articleViewer,
|
||||
@ -963,28 +870,50 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
private fun reloadBadges() {
|
||||
if (displayUnreadCount || displayAllCount) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
if (applicationContext.isNetworkAvailable()) {
|
||||
repository.reloadBadges()
|
||||
reloadBadgeContent()
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.reloadBadges()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadBadgeContent() {
|
||||
private fun handleBadgesContent() {
|
||||
if (displayUnreadCount) {
|
||||
tabNewBadge
|
||||
.setText(repository.badgeUnread.toString())
|
||||
.maybeShow()
|
||||
lifecycleScope.launch {
|
||||
repository.badgeUnread.collect { unreadCount ->
|
||||
if (unreadCount > 0) {
|
||||
tabNewBadge
|
||||
.setText(unreadCount.toString())
|
||||
.maybeShow()
|
||||
} else {
|
||||
tabNewBadge.removeBadge()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (displayAllCount) {
|
||||
tabArchiveBadge
|
||||
.setText(repository.badgeAll.toString())
|
||||
.maybeShow()
|
||||
tabStarredBadge
|
||||
.setText(repository.badgeStarred.toString())
|
||||
.maybeShow()
|
||||
lifecycleScope.launch {
|
||||
repository.badgeAll.collect { itemsCount ->
|
||||
if (itemsCount > 0) {
|
||||
tabArchiveBadge
|
||||
.setText(itemsCount.toString())
|
||||
.maybeShow()
|
||||
} else {
|
||||
tabArchiveBadge.removeBadge()
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
repository.badgeStarred.collect { starredCount ->
|
||||
if (starredCount > 0) {
|
||||
tabStarredBadge
|
||||
.setText(starredCount.toString())
|
||||
.maybeShow()
|
||||
} else {
|
||||
tabStarredBadge.removeBadge()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1041,62 +970,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.refresh -> {
|
||||
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
||||
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
|
||||
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
|
||||
// TODO: Use Dispatchers.IO
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val updatedRemote = repository.updateRemote()
|
||||
if (updatedRemote) {
|
||||
Toast.makeText(
|
||||
this@HomeActivity,
|
||||
R.string.refresh_success_response, Toast.LENGTH_LONG
|
||||
)
|
||||
.show()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this@HomeActivity,
|
||||
R.string.refresh_failer_message,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
|
||||
viewModel.updateRemote()
|
||||
}
|
||||
return true
|
||||
}
|
||||
R.id.readAll -> {
|
||||
if (elementsShown == ItemType.UNREAD) {
|
||||
needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
|
||||
binding.swipeRefreshLayout.isRefreshing = true
|
||||
|
||||
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val success = repository.markAllAsRead(items.map { it.id })
|
||||
if (success) {
|
||||
Toast.makeText(
|
||||
this@HomeActivity,
|
||||
R.string.all_posts_read,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
tabNewBadge.removeBadge()
|
||||
|
||||
handleDrawerItems()
|
||||
|
||||
getElementsAccordingToTab()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this@HomeActivity,
|
||||
R.string.all_posts_not_read,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
handleListResult()
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
}
|
||||
}
|
||||
viewModel.markAllAsRead()
|
||||
}
|
||||
}
|
||||
return true
|
||||
@ -1110,10 +992,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
private fun maxItemNumber(): Int =
|
||||
when (elementsShown) {
|
||||
ItemType.UNREAD -> repository.badgeUnread
|
||||
ItemType.ALL -> repository.badgeAll
|
||||
ItemType.STARRED -> repository.badgeStarred
|
||||
else -> repository.badgeUnread // if !elementsShown then unread are fetched.
|
||||
ItemType.UNREAD -> repository.badgeUnread.value
|
||||
ItemType.ALL -> repository.badgeAll.value
|
||||
ItemType.STARRED -> repository.badgeStarred.value
|
||||
else -> repository.badgeUnread.value // if !elementsShown then unread are fetched.
|
||||
}
|
||||
|
||||
private fun updateItems(adapterItems: ArrayList<SelfossModel.Item>) {
|
||||
@ -1137,30 +1019,4 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
WorkManager.getInstance(baseContext).enqueueUniquePeriodicWork("selfoss-loading", ExistingPeriodicWorkPolicy.KEEP, backgroundWork)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleOfflineActions() {
|
||||
fun doAndReportOnFail(success: Boolean, action: ActionEntity) {
|
||||
if (success) {
|
||||
thread {
|
||||
db.actionsDao().delete(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val actions = db.actionsDao().actions()
|
||||
|
||||
actions.forEach { action ->
|
||||
when {
|
||||
action.read -> doAndReportOnFail(repository.markAsRead(action.articleId.toInt()), action)
|
||||
action.unread -> doAndReportOnFail(repository.markAsRead(action.articleId.toInt()), action)
|
||||
action.starred -> doAndReportOnFail(repository.markAsRead(action.articleId.toInt()), action)
|
||||
action.unstarred -> doAndReportOnFail(repository.markAsRead(action.articleId.toInt()), action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||
import com.russhwolf.settings.Settings
|
||||
@ -202,15 +201,13 @@ class LoginActivity() : AppCompatActivity(), DIAware {
|
||||
|
||||
repository.refreshLoginInformation(url, login, password, httpLogin, httpPassword, isWithSelfSignedCert)
|
||||
|
||||
if (this@LoginActivity.isNetworkAvailable(this@LoginActivity.findViewById(R.id.loginForm))) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val result = repository.login()
|
||||
if (result) {
|
||||
goToMain()
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
preferenceError(Exception("Not success"))
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val result = repository.login()
|
||||
if (result) {
|
||||
goToMain()
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
preferenceError(Exception("Not success"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,32 +7,54 @@ import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import androidx.preference.PreferenceManager
|
||||
import bou.amine.apps.readerforselfossv2.DI.networkModule
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
|
||||
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
|
||||
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
|
||||
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.ftinc.scoop.Scoop
|
||||
import com.github.ln_12.library.ConnectivityStatus
|
||||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||
import com.russhwolf.settings.Settings
|
||||
import io.github.aakira.napier.DebugAntilog
|
||||
import io.github.aakira.napier.Napier
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kodein.di.*
|
||||
|
||||
class MyApp : MultiDexApplication(), DIAware {
|
||||
|
||||
override val di by DI.lazy {
|
||||
import(networkModule)
|
||||
bind<Repository>() with singleton { Repository(instance(), instance()) }
|
||||
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
|
||||
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
|
||||
bind<Repository>() with singleton { Repository(instance(), instance(), connectivityStatus, instance()) }
|
||||
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
|
||||
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
|
||||
}
|
||||
|
||||
private val repository: Repository by instance()
|
||||
private val viewModel: AppViewModel by instance()
|
||||
private val connectivityStatus: ConnectivityStatus by instance()
|
||||
private val driverFactory: DriverFactory by instance()
|
||||
private lateinit var config: Config
|
||||
private lateinit var settings : Settings
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Napier.base(DebugAntilog())
|
||||
config = Config()
|
||||
settings = Settings()
|
||||
|
||||
@ -43,6 +65,35 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
tryToHandleBug()
|
||||
|
||||
handleNotificationChannels()
|
||||
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository))
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
viewModel.networkAvailableProvider.collect { networkAvailable ->
|
||||
val toastMessage = if (networkAvailable) {
|
||||
repository.handleDBActions()
|
||||
R.string.network_connectivity_retrieved
|
||||
} else {
|
||||
R.string.network_connectivity_lost
|
||||
}
|
||||
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
toastMessage,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
viewModel.toastMessageProvider.collect { toastMessage ->
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
toastMessage,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun handleNotificationChannels() {
|
||||
@ -102,4 +153,19 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppLifeCycleObserver(val connectivityStatus: ConnectivityStatus, val repository: Repository) : DefaultLifecycleObserver {
|
||||
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
super.onResume(owner)
|
||||
repository.connectionMonitored = true
|
||||
connectivityStatus.start()
|
||||
}
|
||||
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
repository.connectionMonitored = false
|
||||
connectivityStatus.stop()
|
||||
super.onPause(owner)
|
||||
}
|
||||
}
|
||||
}
|
@ -8,20 +8,14 @@ import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.room.Room
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityReaderBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.fragments.ArticleFragment
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.toggleStar
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import com.ftinc.scoop.Scoop
|
||||
import com.russhwolf.settings.Settings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -39,7 +33,6 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
|
||||
private lateinit var toolbarMenu: Menu
|
||||
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var binding: ActivityReaderBinding
|
||||
|
||||
private var activeAlignment: Int = 1
|
||||
@ -47,7 +40,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
private val ALIGN_LEFT = 2
|
||||
|
||||
override val di by closestDI()
|
||||
private val repository : Repository by instance()
|
||||
private val repository: Repository by instance()
|
||||
|
||||
private fun showMenuItem(willAddToFavorite: Boolean) {
|
||||
if (willAddToFavorite) {
|
||||
@ -75,11 +68,6 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
|
||||
setContentView(view)
|
||||
|
||||
db = Room.databaseBuilder(
|
||||
applicationContext,
|
||||
AppDatabase::class.java, "selfoss-database"
|
||||
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
|
||||
|
||||
val scoop = Scoop.getInstance()
|
||||
scoop.bind(this, Toppings.PRIMARY.value, binding.toolBar)
|
||||
scoop.bindStatusBar(this, Toppings.PRIMARY_DARK.value)
|
||||
@ -111,11 +99,11 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
|
||||
private fun readItem(item: SelfossModel.Item) {
|
||||
if (markOnScroll) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.markAsRead(item.id)
|
||||
// TODO: Handle failure
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.markAsRead(item)
|
||||
// TODO: Handle failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(oldInstanceState: Bundle) {
|
||||
@ -128,19 +116,22 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
|
||||
override fun getItemCount(): Int = allItems.size
|
||||
|
||||
override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position])
|
||||
override fun createFragment(position: Int): Fragment =
|
||||
ArticleFragment.newInstance(allItems[position])
|
||||
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
return when (keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||
val currentFragment = supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
||||
val currentFragment =
|
||||
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
||||
currentFragment.scrollDown()
|
||||
true
|
||||
}
|
||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||
val currentFragment = supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
||||
val currentFragment =
|
||||
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
||||
currentFragment.scrollUp()
|
||||
true
|
||||
}
|
||||
@ -169,19 +160,19 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
alignmentMenu()
|
||||
|
||||
binding.pager.registerOnPageChangeCallback(
|
||||
object : ViewPager2.OnPageChangeCallback() {
|
||||
object : ViewPager2.OnPageChangeCallback() {
|
||||
|
||||
override fun onPageSelected(position: Int) {
|
||||
super.onPageSelected(position)
|
||||
override fun onPageSelected(position: Int) {
|
||||
super.onPageSelected(position)
|
||||
|
||||
if (allItems[position].starred) {
|
||||
canRemoveFromFavorite()
|
||||
} else {
|
||||
canFavorite()
|
||||
}
|
||||
readItem(allItems[position])
|
||||
if (allItems[position].starred) {
|
||||
canRemoveFromFavorite()
|
||||
} else {
|
||||
canFavorite()
|
||||
}
|
||||
readItem(allItems[position])
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return true
|
||||
@ -190,7 +181,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
fun afterSave() {
|
||||
allItems[binding.pager.currentItem] =
|
||||
allItems[binding.pager.currentItem].toggleStar()
|
||||
allItems[binding.pager.currentItem].toggleStar()
|
||||
canRemoveFromFavorite()
|
||||
}
|
||||
|
||||
@ -207,13 +198,13 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
R.id.star -> {
|
||||
if (allItems[binding.pager.currentItem].starred) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.unstarr(allItems[binding.pager.currentItem].id)
|
||||
repository.unstarr(allItems[binding.pager.currentItem])
|
||||
// TODO: Handle failure
|
||||
}
|
||||
afterUnsave()
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.starr(allItems[binding.pager.currentItem].id)
|
||||
repository.starr(allItems[binding.pager.currentItem])
|
||||
// TODO: Handle failure
|
||||
}
|
||||
afterSave()
|
||||
|
@ -10,9 +10,8 @@ import bou.amine.apps.readerforselfossv2.android.adapters.SourcesListAdapter
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySourcesBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import com.ftinc.scoop.Scoop
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -64,30 +63,28 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.layoutManager = mLayoutManager
|
||||
|
||||
if (this@SourcesActivity.isNetworkAvailable(binding.recyclerView)) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val response = repository.getSources()
|
||||
if (response != null) {
|
||||
items = response
|
||||
val mAdapter = SourcesListAdapter(
|
||||
this@SourcesActivity, items
|
||||
)
|
||||
binding.recyclerView.adapter = mAdapter
|
||||
mAdapter.notifyDataSetChanged()
|
||||
if (items.isEmpty()) {
|
||||
Toast.makeText(
|
||||
this@SourcesActivity,
|
||||
R.string.nothing_here,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val response = repository.getSources()
|
||||
if (response != null) {
|
||||
items = response
|
||||
val mAdapter = SourcesListAdapter(
|
||||
this@SourcesActivity, items
|
||||
)
|
||||
binding.recyclerView.adapter = mAdapter
|
||||
mAdapter.notifyDataSetChanged()
|
||||
if (items.isEmpty()) {
|
||||
Toast.makeText(
|
||||
this@SourcesActivity,
|
||||
R.string.cant_get_sources,
|
||||
R.string.nothing_here,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this@SourcesActivity,
|
||||
R.string.cant_get_sources,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,15 +10,16 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.model.*
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.*
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import com.bumptech.glide.Glide
|
||||
@ -32,7 +33,6 @@ import org.kodein.di.instance
|
||||
class ItemCardAdapter(
|
||||
override val app: Activity,
|
||||
override var items: ArrayList<SelfossModel.Item>,
|
||||
override val db: AppDatabase,
|
||||
private val helper: CustomTabActivityHelper,
|
||||
private val internalBrowser: Boolean,
|
||||
private val articleViewer: Boolean,
|
||||
@ -59,7 +59,7 @@ class ItemCardAdapter(
|
||||
val itm = items[position]
|
||||
|
||||
binding.favButton.isSelected = itm.starred
|
||||
binding.title.text = itm.getTitleDecoded()
|
||||
binding.title.text = itm.title.getHtmlDecoded()
|
||||
|
||||
binding.title.setOnTouchListener(LinkOnTouchListener())
|
||||
|
||||
@ -82,13 +82,13 @@ class ItemCardAdapter(
|
||||
}
|
||||
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
val color = generator.getColor(itm.getSourceTitle())
|
||||
val color = generator.getColor(itm.title.getHtmlDecoded())
|
||||
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.getSourceTitle().toTextDrawableString(c), color)
|
||||
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
|
||||
binding.sourceImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.sourceImage)
|
||||
@ -110,28 +110,26 @@ class ItemCardAdapter(
|
||||
|
||||
binding.favButton.setOnClickListener {
|
||||
val item = items[bindingAdapterPosition]
|
||||
if (c.isNetworkAvailable()) {
|
||||
if (item.starred) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.unstarr(item.id)
|
||||
// TODO: Handle failure
|
||||
}
|
||||
item.starred = false
|
||||
binding.favButton.isSelected = false
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.starr(item.id)
|
||||
// TODO: Handle failure
|
||||
}
|
||||
item.starred = true
|
||||
binding.favButton.isSelected = true
|
||||
if (item.starred) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.unstarr(item)
|
||||
// TODO: Handle failure
|
||||
}
|
||||
item.starred = false
|
||||
binding.favButton.isSelected = false
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.starr(item)
|
||||
// TODO: Handle failure
|
||||
}
|
||||
item.starred = true
|
||||
binding.favButton.isSelected = true
|
||||
}
|
||||
}
|
||||
|
||||
binding.shareBtn.setOnClickListener {
|
||||
val item = items[bindingAdapterPosition]
|
||||
c.shareLink(item.getLinkDecoded(), item.getTitleDecoded())
|
||||
c.shareLink(item.getLinkDecoded(), item.title.getHtmlDecoded())
|
||||
}
|
||||
|
||||
binding.browserBtn.setOnClickListener {
|
||||
|
@ -7,14 +7,16 @@ import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.ListItemBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.model.*
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.*
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import org.kodein.di.DI
|
||||
@ -24,7 +26,6 @@ import org.kodein.di.instance
|
||||
class ItemListAdapter(
|
||||
override val app: Activity,
|
||||
override var items: ArrayList<SelfossModel.Item>,
|
||||
override val db: AppDatabase,
|
||||
private val helper: CustomTabActivityHelper,
|
||||
private val internalBrowser: Boolean,
|
||||
private val articleViewer: Boolean,
|
||||
@ -47,7 +48,7 @@ class ItemListAdapter(
|
||||
with(holder) {
|
||||
val itm = items[position]
|
||||
|
||||
binding.title.text = itm.getTitleDecoded()
|
||||
binding.title.text = itm.title.getHtmlDecoded()
|
||||
|
||||
binding.title.setOnTouchListener(LinkOnTouchListener())
|
||||
|
||||
@ -58,13 +59,13 @@ class ItemListAdapter(
|
||||
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
|
||||
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
val color = generator.getColor(itm.getSourceTitle())
|
||||
val color = generator.getColor(itm.title.getHtmlDecoded())
|
||||
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.getSourceTitle().toTextDrawableString(c), color)
|
||||
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
|
||||
|
||||
binding.itemImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
|
@ -5,11 +5,10 @@ import android.graphics.Color
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -20,7 +19,6 @@ import org.kodein.di.DIAware
|
||||
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>(), DIAware {
|
||||
abstract var items: ArrayList<SelfossModel.Item>
|
||||
abstract val repository: Repository
|
||||
abstract val db: AppDatabase
|
||||
abstract val app: Activity
|
||||
abstract val appColors: AppColors
|
||||
abstract val config: Config
|
||||
@ -79,7 +77,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
||||
private fun readItemAtIndex(position: Int, showSnackbar: Boolean = true) {
|
||||
val i = items[position]
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.markAsRead(i.id)
|
||||
repository.markAsRead(i)
|
||||
}
|
||||
if (repository.displayedItems == ItemType.UNREAD) {
|
||||
items.remove(i)
|
||||
@ -95,7 +93,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
||||
|
||||
private fun unreadItemAtIndex(position: Int, showSnackbar: Boolean = true) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.unmarkAsRead(items[position].id)
|
||||
repository.unmarkAsRead(items[position])
|
||||
// Todo: SharedItems.unreadItem(app, api, db, items[position])
|
||||
// TODO: update db
|
||||
|
||||
|
@ -10,14 +10,13 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.model.getIcon
|
||||
import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded
|
||||
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.toTextDrawableString
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.amulyakhare.textdrawable.util.ColorGenerator
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -50,19 +49,19 @@ class SourcesListAdapter(
|
||||
config = Config()
|
||||
|
||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||
val color = generator.getColor(itm.getTitleDecoded())
|
||||
val color = generator.getColor(itm.title.getHtmlDecoded())
|
||||
|
||||
val drawable =
|
||||
TextDrawable
|
||||
.builder()
|
||||
.round()
|
||||
.build(itm.getTitleDecoded().toTextDrawableString(c), color)
|
||||
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
|
||||
binding.itemImage.setImageDrawable(drawable)
|
||||
} else {
|
||||
c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||
}
|
||||
|
||||
binding.sourceTitle.text = itm.getTitleDecoded()
|
||||
binding.sourceTitle.text = itm.title.getHtmlDecoded()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
@ -78,21 +77,19 @@ class SourcesListAdapter(
|
||||
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
|
||||
|
||||
deleteBtn.setOnClickListener {
|
||||
if (c.isNetworkAvailable(null)) {
|
||||
val (id) = items[adapterPosition]
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val successfullyDeletedSource = repository.deleteSource(id)
|
||||
if (successfullyDeletedSource) {
|
||||
items.removeAt(adapterPosition)
|
||||
notifyItemRemoved(adapterPosition)
|
||||
notifyItemRangeChanged(adapterPosition, itemCount)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
app,
|
||||
R.string.can_delete_source,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
val (id) = items[adapterPosition]
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val successfullyDeletedSource = repository.deleteSource(id)
|
||||
if (successfullyDeletedSource) {
|
||||
items.removeAt(adapterPosition)
|
||||
notifyItemRemoved(adapterPosition)
|
||||
notifyItemRangeChanged(adapterPosition, itemCount)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
app,
|
||||
R.string.can_delete_source,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class MercuryApi() {
|
||||
val retrofit =
|
||||
Retrofit
|
||||
.Builder()
|
||||
.baseUrl("https://www.amine-bou.fr")
|
||||
.baseUrl("https://www.amine-louveau.fr")
|
||||
.client(client)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.build()
|
||||
|
@ -8,23 +8,17 @@ import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
|
||||
import androidx.core.app.NotificationCompat.PRIORITY_LOW
|
||||
import androidx.room.Room
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import bou.amine.apps.readerforselfossv2.android.MainActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.MyApp
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.model.preloadImages
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
|
||||
import bou.amine.apps.readerforselfossv2.dao.ACTION
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||
import com.russhwolf.settings.Settings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -36,7 +30,6 @@ import kotlin.concurrent.schedule
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params), DIAware {
|
||||
lateinit var db: AppDatabase
|
||||
|
||||
override val di by lazy { (applicationContext as MyApp).di }
|
||||
private val repository : Repository by instance()
|
||||
@ -44,67 +37,29 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con
|
||||
override fun doWork(): Result {
|
||||
val settings = Settings()
|
||||
val periodicRefresh = settings.getBoolean("periodic_refresh", false)
|
||||
if (periodicRefresh) {
|
||||
if (periodicRefresh && isNetworkAccessible(context)) {
|
||||
|
||||
if (context.isNetworkAvailable()) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val notificationManager =
|
||||
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val notificationManager =
|
||||
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val notification =
|
||||
NotificationCompat.Builder(applicationContext, Config.syncChannelId)
|
||||
.setContentTitle(context.getString(R.string.loading_notification_title))
|
||||
.setContentText(context.getString(R.string.loading_notification_text))
|
||||
.setOngoing(true)
|
||||
.setPriority(PRIORITY_LOW)
|
||||
.setChannelId(Config.syncChannelId)
|
||||
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
|
||||
|
||||
val notification =
|
||||
NotificationCompat.Builder(applicationContext, Config.syncChannelId)
|
||||
.setContentTitle(context.getString(R.string.loading_notification_title))
|
||||
.setContentText(context.getString(R.string.loading_notification_text))
|
||||
.setOngoing(true)
|
||||
.setPriority(PRIORITY_LOW)
|
||||
.setChannelId(Config.syncChannelId)
|
||||
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
|
||||
notificationManager.notify(1, notification.build())
|
||||
|
||||
notificationManager.notify(1, notification.build())
|
||||
val notifyNewItems = settings.getBoolean("notify_new_items", false)
|
||||
|
||||
val notifyNewItems = settings.getBoolean("notify_new_items", false)
|
||||
repository.handleDBActions()
|
||||
|
||||
db = Room.databaseBuilder(
|
||||
applicationContext,
|
||||
AppDatabase::class.java, "selfoss-database"
|
||||
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3)
|
||||
.addMigrations(MIGRATION_3_4).build()
|
||||
|
||||
val actions = db.actionsDao().actions()
|
||||
|
||||
actions.forEach { action ->
|
||||
when {
|
||||
action.read -> doAndReportOnFail(
|
||||
repository.markAsRead(action.articleId.toInt()),
|
||||
action
|
||||
)
|
||||
action.unread -> doAndReportOnFail(
|
||||
repository.unmarkAsRead(action.articleId.toInt()),
|
||||
action
|
||||
)
|
||||
action.starred -> doAndReportOnFail(
|
||||
repository.starr(action.articleId.toInt()),
|
||||
action
|
||||
)
|
||||
action.unstarred -> doAndReportOnFail(
|
||||
repository.unstarr(action.articleId.toInt()),
|
||||
action
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (context.isNetworkAvailable()) {
|
||||
launch {
|
||||
try {
|
||||
val newItems = repository.allItems(ItemType.UNREAD)
|
||||
handleNewItemsNotification(newItems, notifyNewItems, notificationManager)
|
||||
val readItems = repository.allItems(ItemType.ALL)
|
||||
val starredItems = repository.allItems(ItemType.STARRED)
|
||||
// TODO: save all to DB
|
||||
} catch (e: Throwable) {}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
handleNewItemsNotification(repository.tryToCacheItemsAndGetNewOnes(), notifyNewItems, notificationManager)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -158,13 +113,4 @@ override fun doWork(): Result {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun doAndReportOnFail(result: Boolean, action: ActionEntity) {
|
||||
// TODO: Failures should be reported
|
||||
if (result) {
|
||||
thread {
|
||||
db.actionsDao().delete(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,25 +19,22 @@ import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.room.Room
|
||||
import bou.amine.apps.readerforselfossv2.android.ImageActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.api.mercury.MercuryApi
|
||||
import bou.amine.apps.readerforselfossv2.android.api.mercury.ParsedContent
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.model.*
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
||||
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.*
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import bou.amine.apps.readerforselfossv2.utils.getImages
|
||||
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
|
||||
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
@ -72,7 +69,6 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
private lateinit var allImages : ArrayList<String>
|
||||
private lateinit var fab: FloatingActionButton
|
||||
private lateinit var appColors: AppColors
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var textAlignment: String
|
||||
private lateinit var config: Config
|
||||
private var _binding: FragmentArticleBinding? = null
|
||||
@ -104,11 +100,6 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
val pi: ParecelableItem = requireArguments().getParcelable(ARG_ITEMS)!!
|
||||
|
||||
item = pi.toModel()
|
||||
|
||||
db = Room.databaseBuilder(
|
||||
requireContext(),
|
||||
AppDatabase::class.java, "selfoss-database"
|
||||
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
@ -121,7 +112,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
|
||||
url = item.getLinkDecoded()
|
||||
contentText = item.content
|
||||
contentTitle = item.getTitleDecoded()
|
||||
contentTitle = item.title.getHtmlDecoded()
|
||||
contentImage = item.getThumbnail(repository.baseUrl)
|
||||
contentSource = item.sourceAndDateText(repository.dateUtils)
|
||||
allImages = item.getImages()
|
||||
@ -135,7 +126,6 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
typeface = try {
|
||||
ResourcesCompat.getFont(requireContext(), resId)!!
|
||||
} catch (e: java.lang.Exception) {
|
||||
// ACRA.getErrorReporter().maybeHandleSilentException(Throwable("Font loading issue: ${e.message}"), requireContext())
|
||||
// Just to be sure
|
||||
null
|
||||
}
|
||||
@ -169,7 +159,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
R.id.unread_action -> if (context != null) {
|
||||
if (this@ArticleFragment.item.unread) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.markAsRead(this@ArticleFragment.item.id)
|
||||
repository.markAsRead(this@ArticleFragment.item)
|
||||
}
|
||||
this@ArticleFragment.item.unread = false
|
||||
Toast.makeText(
|
||||
@ -179,7 +169,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
).show()
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.unmarkAsRead(this@ArticleFragment.item.id)
|
||||
repository.unmarkAsRead(this@ArticleFragment.item)
|
||||
}
|
||||
this@ArticleFragment.item.unread = true
|
||||
Toast.makeText(
|
||||
@ -276,9 +266,9 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
}
|
||||
|
||||
private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) {
|
||||
if ((context != null && requireContext().isNetworkAvailable(null)) || context == null) {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
val parser = MercuryApi()
|
||||
if (repository.isNetworkAvailable()) {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
val parser = MercuryApi()
|
||||
|
||||
parser.parseUrl(url).enqueue(
|
||||
object : Callback<ParsedContent> {
|
||||
@ -317,7 +307,10 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
Glide
|
||||
.with(requireContext())
|
||||
.asBitmap()
|
||||
.loadMaybeBasicAuth(config, response.body()!!.lead_image_url.orEmpty())
|
||||
.loadMaybeBasicAuth(
|
||||
config,
|
||||
response.body()!!.lead_image_url.orEmpty()
|
||||
)
|
||||
.apply(RequestOptions.fitCenterTransform())
|
||||
.into(binding.imageView)
|
||||
} catch (e: Exception) {
|
||||
|
@ -1,43 +1,12 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.model
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.text.Html
|
||||
import android.webkit.URLUtil
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.getImages
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import org.jsoup.Jsoup
|
||||
import java.util.*
|
||||
|
||||
|
||||
/**
|
||||
* Items extension methods
|
||||
*/
|
||||
fun SelfossModel.Item.getIcon(baseUrl: String): String {
|
||||
return constructUrl(baseUrl, "favicons", icon)
|
||||
}
|
||||
|
||||
fun SelfossModel.Item.getThumbnail(baseUrl: String): String {
|
||||
return constructUrl(baseUrl, "thumbnails", thumbnail)
|
||||
}
|
||||
|
||||
fun SelfossModel.Item.getImages() : ArrayList<String> {
|
||||
val allImages = ArrayList<String>()
|
||||
|
||||
for ( image in Jsoup.parse(content).getElementsByTag("img")) {
|
||||
val url = image.attr("src")
|
||||
if (url.lowercase(Locale.US).contains(".jpg") ||
|
||||
url.lowercase(Locale.US).contains(".jpeg") ||
|
||||
url.lowercase(Locale.US).contains(".png") ||
|
||||
url.lowercase(Locale.US).contains(".webp"))
|
||||
{
|
||||
allImages.add(url)
|
||||
}
|
||||
}
|
||||
return allImages
|
||||
}
|
||||
|
||||
fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
|
||||
val imageUrls = this.getImages()
|
||||
@ -60,66 +29,14 @@ fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
fun SelfossModel.Item.getTitleDecoded(): String {
|
||||
return Html.fromHtml(title).toString()
|
||||
}
|
||||
|
||||
fun SelfossModel.Item.getSourceTitle(): String {
|
||||
return Html.fromHtml(sourcetitle).toString()
|
||||
}
|
||||
|
||||
// TODO: maybe find a better way to handle these kind of urls
|
||||
fun SelfossModel.Item.getLinkDecoded(): String {
|
||||
var stringUrl: String
|
||||
stringUrl =
|
||||
if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
|
||||
if (link.contains("&url=")) {
|
||||
link.substringAfter("&url=")
|
||||
} else {
|
||||
this.link.replace("&", "&")
|
||||
}
|
||||
} else {
|
||||
this.link.replace("&", "&")
|
||||
fun String.toTextDrawableString(): String {
|
||||
val textDrawable = StringBuilder()
|
||||
for (s in this.split(" ".toRegex()).filter { it.isNotEmpty() }.toTypedArray()) {
|
||||
try {
|
||||
textDrawable.append(s[0])
|
||||
} catch (e: StringIndexOutOfBoundsException) {
|
||||
// We do nothing
|
||||
}
|
||||
|
||||
// handle :443 => https
|
||||
if (stringUrl.contains(":443")) {
|
||||
stringUrl = stringUrl.replace(":443", "").replace("http://", "https://")
|
||||
}
|
||||
|
||||
// handle url not starting with http
|
||||
if (stringUrl.startsWith("//")) {
|
||||
stringUrl = "http:$stringUrl"
|
||||
}
|
||||
|
||||
return stringUrl
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sources extension methods
|
||||
*/
|
||||
|
||||
fun SelfossModel.Source.getIcon(baseUrl: String): String {
|
||||
return constructUrl(baseUrl, "favicons", icon)
|
||||
}
|
||||
|
||||
fun SelfossModel.Source.getTitleDecoded(): String {
|
||||
return Html.fromHtml(title).toString()
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Common methods
|
||||
*/
|
||||
private fun constructUrl(baseUrl: String, path: String, file: String?): String {
|
||||
return if (file == null || file == "null" || file.isEmpty()) {
|
||||
""
|
||||
} else {
|
||||
val baseUriBuilder = Uri.parse(baseUrl).buildUpon()
|
||||
baseUriBuilder.appendPath(path).appendPath(file)
|
||||
|
||||
baseUriBuilder.toString()
|
||||
}
|
||||
return textDrawable.toString()
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.model
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.RequiresApi
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
fun SelfossModel.Item.toParcelable() : ParecelableItem =
|
||||
|
@ -1,28 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.persistence
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
|
||||
import bou.amine.apps.readerforselfossv2.dao.DeviceDatabase
|
||||
|
||||
class AndroidDeviceDatabase(applicationContext: Context): DeviceDatabase<AndroidItemEntity> {
|
||||
var db: AppDatabase = Room.databaseBuilder(
|
||||
applicationContext,
|
||||
AppDatabase::class.java, "selfoss-database"
|
||||
).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build()
|
||||
|
||||
|
||||
override suspend fun items(): List<AndroidItemEntity> = db.itemsDao().items()
|
||||
|
||||
override suspend fun insertAllItems(vararg items: AndroidItemEntity) = db.itemsDao().insertAllItems(*items)
|
||||
|
||||
override suspend fun deleteAllItems() = db.itemsDao().deleteAllItems()
|
||||
|
||||
override suspend fun delete(item: AndroidItemEntity) = db.itemsDao().delete(item)
|
||||
|
||||
override suspend fun updateItem(item: AndroidItemEntity) = db.itemsDao().updateItem(item)
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.persistence
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toEntity
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.service.DeviceDataBaseService
|
||||
import bou.amine.apps.readerforselfossv2.service.SearchService
|
||||
|
||||
class AndroidDeviceDatabaseService(db: AndroidDeviceDatabase, searchService: SearchService) :
|
||||
DeviceDataBaseService<AndroidItemEntity>(db, searchService) {
|
||||
override suspend fun updateDatabase() {
|
||||
if (itemsCaching) {
|
||||
if (items.isEmpty()) {
|
||||
getFromDB()
|
||||
}
|
||||
db.deleteAllItems()
|
||||
db.insertAllItems(*(items.map { it.toEntity() }).toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clearDBItems() {
|
||||
db.deleteAllItems()
|
||||
}
|
||||
|
||||
override fun appendNewItems(newItems: List<SelfossModel.Item>) {
|
||||
var oldItems = items
|
||||
if (oldItems != newItems) {
|
||||
oldItems = oldItems.filter { item -> newItems.find { it.id == item.id } == null } as ArrayList<SelfossModel.Item>
|
||||
oldItems.addAll(newItems)
|
||||
items = oldItems
|
||||
|
||||
sortItems()
|
||||
getFocusedItems()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFromDB() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.persistence.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
|
||||
|
||||
@Dao
|
||||
interface ActionsDao {
|
||||
@Query("SELECT * FROM actions order by id asc")
|
||||
suspend fun actions(): List<ActionEntity>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertAllActions(vararg actions: ActionEntity)
|
||||
|
||||
@Query("DELETE FROM actions WHERE articleid = :article_id AND read = 1")
|
||||
fun deleteReadActionForArticle(article_id: String)
|
||||
|
||||
@Delete
|
||||
fun delete(action: ActionEntity)
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.persistence.dao
|
||||
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.SourceEntity
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.TagEntity
|
||||
|
||||
@Dao
|
||||
interface DrawerDataDao {
|
||||
@Query("SELECT * FROM tags")
|
||||
fun tags(): List<TagEntity>
|
||||
|
||||
@Query("SELECT * FROM sources")
|
||||
fun sources(): List<SourceEntity>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertAllTags(vararg tags: TagEntity)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertAllSources(vararg sources: SourceEntity)
|
||||
|
||||
@Query("DELETE FROM tags")
|
||||
fun deleteAllTags()
|
||||
|
||||
@Query("DELETE FROM sources")
|
||||
fun deleteAllSources()
|
||||
|
||||
@Delete
|
||||
fun deleteTag(tag: TagEntity)
|
||||
|
||||
@Delete
|
||||
fun deleteSource(source: SourceEntity)
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.persistence.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
|
||||
import androidx.room.Update
|
||||
|
||||
|
||||
|
||||
@Dao
|
||||
interface ItemsDao {
|
||||
@Query("SELECT * FROM items order by id desc")
|
||||
suspend fun items(): List<AndroidItemEntity>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertAllItems(vararg items: AndroidItemEntity)
|
||||
|
||||
@Query("DELETE FROM items")
|
||||
suspend fun deleteAllItems()
|
||||
|
||||
@Delete
|
||||
suspend fun delete(item: AndroidItemEntity)
|
||||
|
||||
@Update
|
||||
suspend fun updateItem(item: AndroidItemEntity)
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.persistence.database
|
||||
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.Database
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.dao.ActionsDao
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.dao.DrawerDataDao
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.dao.ItemsDao
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.SourceEntity
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.TagEntity
|
||||
|
||||
@Database(entities = [TagEntity::class, SourceEntity::class, AndroidItemEntity::class, ActionEntity::class], version = 4)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun drawerDataDao(): DrawerDataDao
|
||||
|
||||
abstract fun itemsDao(): ItemsDao
|
||||
|
||||
abstract fun actionsDao(): ActionsDao
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.persistence.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "actions")
|
||||
data class ActionEntity(
|
||||
@ColumnInfo(name = "articleid")
|
||||
val articleId: String,
|
||||
@ColumnInfo(name = "read")
|
||||
val read: Boolean,
|
||||
@ColumnInfo(name = "unread")
|
||||
val unread: Boolean,
|
||||
@ColumnInfo(name = "starred")
|
||||
var starred: Boolean,
|
||||
@ColumnInfo(name = "unstarred")
|
||||
var unstarred: Boolean
|
||||
) {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
var id: Int = 0
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.persistence.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
|
||||
@Entity(tableName = "items")
|
||||
data class AndroidItemEntity(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "id")
|
||||
val id: String,
|
||||
@ColumnInfo(name = "datetime")
|
||||
val datetime: String,
|
||||
@ColumnInfo(name = "title")
|
||||
val title: String,
|
||||
@ColumnInfo(name = "content")
|
||||
val content: String,
|
||||
@ColumnInfo(name = "unread")
|
||||
val unread: Boolean,
|
||||
@ColumnInfo(name = "starred")
|
||||
var starred: Boolean,
|
||||
@ColumnInfo(name = "thumbnail")
|
||||
val thumbnail: String?,
|
||||
@ColumnInfo(name = "icon")
|
||||
val icon: String?,
|
||||
@ColumnInfo(name = "link")
|
||||
val link: String,
|
||||
@ColumnInfo(name = "sourcetitle")
|
||||
val sourcetitle: String,
|
||||
@ColumnInfo(name = "tags")
|
||||
val tags: String
|
||||
)
|
@ -1,33 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.persistence.entities
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "tags")
|
||||
data class TagEntity(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "tag")
|
||||
val tag: String,
|
||||
@ColumnInfo(name = "color")
|
||||
val color: String,
|
||||
@ColumnInfo(name = "unread")
|
||||
val unread: Int
|
||||
)
|
||||
|
||||
@Entity(tableName = "sources")
|
||||
data class SourceEntity(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "id")
|
||||
val id: String,
|
||||
@ColumnInfo(name = "title")
|
||||
val title: String,
|
||||
@ColumnInfo(name = "tags")
|
||||
val tags: String,
|
||||
@ColumnInfo(name = "spout")
|
||||
val spout: String,
|
||||
@ColumnInfo(name = "error")
|
||||
val error: String,
|
||||
@ColumnInfo(name = "icon")
|
||||
val icon: String
|
||||
)
|
@ -1,34 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.persistence.migrations
|
||||
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import androidx.room.migration.Migration
|
||||
|
||||
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `items` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT NOT NULL, `icon` TEXT NOT NULL, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))")
|
||||
}
|
||||
}
|
||||
|
||||
val MIGRATION_2_3: Migration = object : Migration(2, 3) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `actions` (`id` INTEGER NOT NULL, `articleid` TEXT NOT NULL, `read` INTEGER NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `unstarred` INTEGER NOT NULL, PRIMARY KEY(`id`))")
|
||||
}
|
||||
}
|
||||
|
||||
val MIGRATION_3_4: Migration = object : Migration(3, 4) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
// @see https://stackoverflow.com/questions/57392015/how-to-migrate-not-null-table-column-into-null-in-android-room-database
|
||||
// Create the new table
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS `itemstmp` (`id` TEXT NOT NULL, `datetime` TEXT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL, `unread` INTEGER NOT NULL, `starred` INTEGER NOT NULL, `thumbnail` TEXT, `icon` TEXT, `link` TEXT NOT NULL, `sourcetitle` TEXT NOT NULL, `tags` TEXT NOT NULL, PRIMARY KEY(`id`))")
|
||||
|
||||
// Copy the data
|
||||
database.execSQL(
|
||||
"INSERT INTO itemstmp (`id`, `datetime`, `title`, `content`, `unread`, `starred`, `thumbnail`, `icon`, `link`, `sourcetitle`, `tags`) SELECT `id`, `datetime`, `title`, `content`, `unread`, `starred`, `thumbnail`, `icon`, `link`, `sourcetitle`, `tags` FROM items")
|
||||
|
||||
// Remove the old table
|
||||
database.execSQL("DROP TABLE items")
|
||||
|
||||
// Change the table name to the correct one
|
||||
database.execSQL("ALTER TABLE itemstmp RENAME TO items")
|
||||
}
|
||||
}
|
@ -46,16 +46,16 @@ class AppColors(a: Activity) {
|
||||
|
||||
colorBackground = if (isDarkTheme) {
|
||||
a.setTheme(R.style.NoBarDark)
|
||||
R.color.darkBackground
|
||||
a.resources.getColor(R.color.darkBackground)
|
||||
} else {
|
||||
a.setTheme(R.style.NoBar)
|
||||
R.color.grey_50
|
||||
a.resources.getColor(R.color.grey_50)
|
||||
}
|
||||
|
||||
textColor = if (isDarkTheme) {
|
||||
R.color.white
|
||||
a.resources.getColor(R.color.white)
|
||||
} else {
|
||||
R.color.grey_900
|
||||
a.resources.getColor(R.color.grey_900)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,13 +28,13 @@ class Config {
|
||||
companion object {
|
||||
const val settingsName = "paramsselfoss"
|
||||
|
||||
const val feedbackEmail = "aminecmi@gmail.com"
|
||||
const val feedbackEmail = "aminecmi@pm.me.com"
|
||||
|
||||
const val translationUrl = "https://crwd.in/readerforselfoss"
|
||||
|
||||
const val sourceUrl = "https://github.com/aminecmi/ReaderforSelfoss"
|
||||
const val sourceUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform"
|
||||
|
||||
const val trackerUrl = "https://github.com/aminecmi/ReaderforSelfoss/issues"
|
||||
const val trackerUrl = "https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
||||
|
||||
const val syncChannelId = "sync-channel-id"
|
||||
|
||||
|
@ -1,29 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.utils
|
||||
|
||||
import android.content.Context
|
||||
import bou.amine.apps.readerforselfossv2.android.model.getSourceTitle
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||
import bou.amine.apps.readerforselfossv2.utils.parseRelativeDate
|
||||
|
||||
fun String.toTextDrawableString(c: Context): String {
|
||||
val textDrawable = StringBuilder()
|
||||
for (s in this.split(" ".toRegex()).filter { it.isNotEmpty() }.toTypedArray()) {
|
||||
try {
|
||||
textDrawable.append(s[0])
|
||||
} catch (e: StringIndexOutOfBoundsException) {
|
||||
}
|
||||
}
|
||||
return textDrawable.toString()
|
||||
}
|
||||
|
||||
fun SelfossModel.Item.sourceAndDateText(dateUtils: DateUtils): String {
|
||||
val formattedDate = parseRelativeDate(dateUtils)
|
||||
|
||||
return getSourceTitle() + formattedDate
|
||||
}
|
||||
|
||||
fun SelfossModel.Item.toggleStar(): SelfossModel.Item {
|
||||
this.starred = !this.starred
|
||||
return this
|
||||
}
|
@ -18,9 +18,8 @@ import android.widget.Toast
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.ReaderActivity
|
||||
import bou.amine.apps.readerforselfossv2.android.model.getLinkDecoded
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
|
||||
|
@ -1,9 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.utils
|
||||
|
||||
import android.content.res.Resources
|
||||
|
||||
val Int.toPx: Int
|
||||
get() = (this * Resources.getSystem().displayMetrics.density).toInt()
|
||||
|
||||
val Int.toDp: Int
|
||||
get() = (this / Resources.getSystem().displayMetrics.density).toInt()
|
@ -1,52 +1,14 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.utils.network
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
var snackBarShown = false
|
||||
var view: View? = null
|
||||
lateinit var s: Snackbar
|
||||
|
||||
fun Context.isNetworkAvailable(
|
||||
v: View? = null,
|
||||
overrideOffline: Boolean = false
|
||||
): Boolean {
|
||||
val networkIsAccessible = isNetworkAccessible(this)
|
||||
|
||||
if (v != null && (!networkIsAccessible || overrideOffline) && (!snackBarShown || v != view)) {
|
||||
view = v
|
||||
s = Snackbar
|
||||
.make(
|
||||
v,
|
||||
R.string.no_network_connectivity,
|
||||
Snackbar.LENGTH_INDEFINITE
|
||||
)
|
||||
|
||||
s.setAction(android.R.string.ok) {
|
||||
snackBarShown = false
|
||||
s.dismiss()
|
||||
}
|
||||
|
||||
val view = s.view
|
||||
val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text)
|
||||
tv.setTextColor(Color.WHITE)
|
||||
s.show()
|
||||
snackBarShown = true
|
||||
}
|
||||
if (snackBarShown && networkIsAccessible && !overrideOffline) {
|
||||
s.dismiss()
|
||||
}
|
||||
return if(overrideOffline) overrideOffline else networkIsAccessible
|
||||
}
|
||||
|
||||
private fun isNetworkAccessible(context: Context): Boolean {
|
||||
fun isNetworkAccessible(context: Context): Boolean {
|
||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
|
@ -1,72 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.utils.persistence
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.android.model.getSourceTitle
|
||||
import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.SourceEntity
|
||||
import bou.amine.apps.readerforselfossv2.android.persistence.entities.TagEntity
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
|
||||
fun TagEntity.toView(): SelfossModel.Tag =
|
||||
SelfossModel.Tag(
|
||||
this.tag,
|
||||
this.color,
|
||||
this.unread
|
||||
)
|
||||
|
||||
fun SourceEntity.toView(): SelfossModel.Source =
|
||||
SelfossModel.Source(
|
||||
this.id.toInt(),
|
||||
this.title,
|
||||
this.tags.split(","),
|
||||
this.spout,
|
||||
this.error,
|
||||
this.icon
|
||||
)
|
||||
|
||||
fun SelfossModel.Source.toEntity(): SourceEntity =
|
||||
SourceEntity(
|
||||
this.id.toString(),
|
||||
this.getTitleDecoded(),
|
||||
this.tags.joinToString(","),
|
||||
this.spout,
|
||||
this.error,
|
||||
this.icon.orEmpty()
|
||||
)
|
||||
|
||||
fun SelfossModel.Tag.toEntity(): TagEntity =
|
||||
TagEntity(
|
||||
this.tag,
|
||||
this.color,
|
||||
this.unread
|
||||
)
|
||||
|
||||
fun AndroidItemEntity.toView(): SelfossModel.Item =
|
||||
SelfossModel.Item(
|
||||
this.id.toInt(),
|
||||
this.datetime,
|
||||
this.title,
|
||||
this.content,
|
||||
this.unread,
|
||||
this.starred,
|
||||
this.thumbnail,
|
||||
this.icon,
|
||||
this.link,
|
||||
this.sourcetitle,
|
||||
this.tags.split(",")
|
||||
)
|
||||
|
||||
fun SelfossModel.Item.toEntity(): AndroidItemEntity =
|
||||
AndroidItemEntity(
|
||||
this.id.toString(),
|
||||
this.datetime,
|
||||
this.getTitleDecoded(),
|
||||
this.content,
|
||||
this.unread,
|
||||
this.starred,
|
||||
this.thumbnail,
|
||||
this.icon,
|
||||
this.link,
|
||||
this.getSourceTitle(),
|
||||
this.tags.joinToString(",")
|
||||
)
|
@ -0,0 +1,83 @@
|
||||
package bou.amine.apps.readerforselfossv2.android.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class AppViewModel(private val repository: Repository) : ViewModel() {
|
||||
private val _networkAvailableProvider = MutableSharedFlow<Boolean>()
|
||||
val networkAvailableProvider = _networkAvailableProvider.asSharedFlow()
|
||||
private val _refreshingIndicatorProvider = MutableSharedFlow<Boolean>()
|
||||
val refreshingIndicatorProvider = _refreshingIndicatorProvider.asSharedFlow()
|
||||
private val _toastMessageProvider = MutableSharedFlow<Int>()
|
||||
val toastMessageProvider = _toastMessageProvider.asSharedFlow()
|
||||
private var wasConnected = true
|
||||
|
||||
private val _items = MutableStateFlow(ArrayList<SelfossModel.Item>())
|
||||
val items = _items.asStateFlow()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
repository.isConnectionAvailable.collect { isConnected ->
|
||||
if (repository.connectionMonitored) {
|
||||
if (isConnected && !wasConnected && repository.connectionMonitored) {
|
||||
_networkAvailableProvider.emit(true)
|
||||
wasConnected = true
|
||||
} else if (!isConnected && wasConnected && repository.connectionMonitored){
|
||||
_networkAvailableProvider.emit(false)
|
||||
wasConnected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateRemote() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
_toastMessageProvider.emit(R.string.refresh_in_progress)
|
||||
val updatedRemote = repository.updateRemote()
|
||||
if (updatedRemote) {
|
||||
_toastMessageProvider.emit(R.string.refresh_success_response)
|
||||
} else {
|
||||
_toastMessageProvider.emit(R.string.refresh_failer_message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getItems(appendResults: Boolean, itemType: ItemType) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
_refreshingIndicatorProvider.emit(true)
|
||||
repository.displayedItems = itemType
|
||||
val items = if (appendResults) {
|
||||
repository.getOlderItems()
|
||||
} else {
|
||||
repository.getNewerItems()
|
||||
}
|
||||
_items.emit(items)
|
||||
_refreshingIndicatorProvider.emit(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun markAllAsRead() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
_refreshingIndicatorProvider.emit(true)
|
||||
val success = repository.markAllAsRead(items.value)
|
||||
if (success) {
|
||||
_toastMessageProvider.emit(R.string.all_posts_read)
|
||||
} else {
|
||||
_toastMessageProvider.emit(R.string.all_posts_not_read)
|
||||
}
|
||||
_refreshingIndicatorProvider.emit(false)
|
||||
}
|
||||
}
|
||||
}
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"Inicieu la sessió per afegir fonts."</string>
|
||||
<string name="cant_get_sources">"No es pot obtenir la llista de fonts."</string>
|
||||
<string name="cant_create_source">"No es pot crear la font."</string>
|
||||
<string name="cant_get_spouts">"No es pot obtenir la llista de canals."</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"El formulari no està complet"</string>
|
||||
<string name="pref_header_links">"Enllaços"</string>
|
||||
<string name="issue_tracker_link">"Detector de problemes"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Sense connexió!</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sincronitza els articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Els articles no se sincronitzaran en segon pla</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Els articles se sincronitzaran periòdicament</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"Melde dich an um Quellen hinzuzufügen."</string>
|
||||
<string name="cant_get_sources">"Quellen können nicht abgerufen werden."</string>
|
||||
<string name="cant_create_source">"Quelle kann nicht gespeichert werden."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list."</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"Das Formular ist nicht vollständig"</string>
|
||||
<string name="pref_header_links">"Links"</string>
|
||||
<string name="issue_tracker_link">"Issue Tracker"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Nicht verbunden !</string>
|
||||
<string name="network_connectivity_lost">"Die Netzwerkverbindung wurde unterbrochen"</string>
|
||||
<string name="network_connectivity_retrieved">"Netzwerkverbindung ist jetzt verfügbar"</string>
|
||||
<string name="pref_switch_periodic_refresh">Synchronisiere Artikel</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Artikel werden nicht im Hintergrund synchronisiert</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Die Artikel werden regelmäßig synchronisiert</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"Iniciar sesión para añadir fuentes."</string>
|
||||
<string name="cant_get_sources">"No se puede obtener la lista de fuentes."</string>
|
||||
<string name="cant_create_source">"No se puede crear la fuente."</string>
|
||||
<string name="cant_get_spouts">"No se puede obtener la lista de fuentes."</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"El formulario no está completo"</string>
|
||||
<string name="pref_header_links">"Enlaces"</string>
|
||||
<string name="issue_tracker_link">"Rastreador de Incidencias"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Sin conexión!</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sincronizar artículos</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Los artículos no se sincronizarán en segundo plano</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Los artículos se sincronizarán periódicamente</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"Log in to add sources."</string>
|
||||
<string name="cant_get_sources">"Can't get sources list."</string>
|
||||
<string name="cant_create_source">"Can't create source."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list."</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"The form is not complete"</string>
|
||||
<string name="pref_header_links">"Links"</string>
|
||||
<string name="issue_tracker_link">"Issue Tracker"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Not connected !</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sync articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||
|
@ -40,13 +40,14 @@
|
||||
<string name="addStringNoUrl">"Identifiez-vous pour ajouter une source."</string>
|
||||
<string name="cant_get_sources">"Impossible de récupérer la liste des sources"</string>
|
||||
<string name="cant_create_source">"Impossible de créer la source."</string>
|
||||
<string name="cant_get_spouts">"Impossible de récupérer vos Spouts pour rajouter des sources"</string>
|
||||
<string name="cant_get_spouts_no_network">"Impossible d'obtenir la liste des spouts en raison d'un problème de réseau."</string>
|
||||
<string name="cant_get_spouts">"Impossible d'obtenir la liste des spouts. Cela pourrait venir de l'api."</string>
|
||||
<string name="form_not_complete">"Il manque des données. Terminez le formulaire."</string>
|
||||
<string name="pref_header_links">"Liens utiles"</string>
|
||||
<string name="issue_tracker_link">"Suivi des problèmes"</string>
|
||||
<string name="issue_tracker_summary">"Pour signaler un bug ou demander une nouvelle fonctionnalité"</string>
|
||||
<string name="warning_wrong_url">"ATTENTION"</string>
|
||||
<string name="pref_switch_card_view_title">"Card View"</string>
|
||||
<string name="pref_switch_card_view_title">"Vue en carte"</string>
|
||||
<string name="cant_mark_favortie">"Impossible de marquer l'élément comme favoris"</string>
|
||||
<string name="cant_unmark_favortie">"Impossible de retirer l'élément des favoris"</string>
|
||||
<string name="share">"Partager"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Vérifier les nouvelles sources et tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Désactivez cette option si votre serveur reçoit trop de requêtes.</string>
|
||||
<string name="no_network_connectivity">Hors connexion !</string>
|
||||
<string name="network_connectivity_lost">"Connexion au réseau perdue"</string>
|
||||
<string name="network_connectivity_retrieved">"Connexion réseau de nouveau disponible"</string>
|
||||
<string name="pref_switch_periodic_refresh">Synchroniser les articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Les articles ne seront pas synchronisés en arrière plan</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Articles seront périodiquement synchronisées</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"Accede pra engadir fontes."</string>
|
||||
<string name="cant_get_sources">"Non se pode obter a lista de fontes."</string>
|
||||
<string name="cant_create_source">"Non se pode crear unha fonte."</string>
|
||||
<string name="cant_get_spouts">"Non se pode obter a lista de fontes."</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"O formulario non está completo"</string>
|
||||
<string name="pref_header_links">"Ligazóns"</string>
|
||||
<string name="issue_tracker_link">"Rastrexador de Incidencias"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Comproba novas fontes e etiquetas</string>
|
||||
<string name="pref_switch_update_sources_summary">Deshabilita isto se o teu servidor está recibindo demasiadas peticións de base de datos.</string>
|
||||
<string name="no_network_connectivity">Non conectado!</string>
|
||||
<string name="network_connectivity_lost">"Perdeuse a conexión de rede"</string>
|
||||
<string name="network_connectivity_retrieved">"Conexión de rede xa dispoñíbel"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sincronizar artigos</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Os artigos non se sincronizarán coa aplicación de fondo</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Os artigos sincronizaranse periódicamente</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"Masuk untuk menambah sumber."</string>
|
||||
<string name="cant_get_sources">"Tidak bisa mendapatkan daftar sumber."</string>
|
||||
<string name="cant_create_source">"Tidak dapat membuat sumber."</string>
|
||||
<string name="cant_get_spouts">"Tidak bisa masuk ke daftar Spouts."</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"Formulirnya belum selesai"</string>
|
||||
<string name="pref_header_links">"Tautan"</string>
|
||||
<string name="issue_tracker_link">"Pelacak Masalah"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Not connected !</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sync articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"Autenticati per aggiungere fonti."</string>
|
||||
<string name="cant_get_sources">"Can't get sources list."</string>
|
||||
<string name="cant_create_source">"Can't create source."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list."</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"Il modulo non è completo"</string>
|
||||
<string name="pref_header_links">"Links"</string>
|
||||
<string name="issue_tracker_link">"Traccia problemi"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Not connected !</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sync articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"로그인 소스를 추가 해야 합니다."</string>
|
||||
<string name="cant_get_sources">"소스 리스트를 얻을 수 없습니다."</string>
|
||||
<string name="cant_create_source">"소스를 만들 수 없습니다."</string>
|
||||
<string name="cant_get_spouts">"Spouts 목록을 가져올 수 없습니다."</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"양식이 완료되지 않았습니다."</string>
|
||||
<string name="pref_header_links">"링크"</string>
|
||||
<string name="issue_tracker_link">"이슈 트래커"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Not connected !</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sync articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"Login om bronnen toe te voegen"</string>
|
||||
<string name="cant_get_sources">"Kan de lijst met bronnen niet ophalen"</string>
|
||||
<string name="cant_create_source">"Kan bron niet creëeren"</string>
|
||||
<string name="cant_get_spouts">"Ophalen spouts mislukt"</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"Formulier is niet volledig ingevuld"</string>
|
||||
<string name="pref_header_links">"Links"</string>
|
||||
<string name="issue_tracker_link">"Bug tracker"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Not connected !</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sync articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"Faça login para adicionar fontes."</string>
|
||||
<string name="cant_get_sources">"Não é possível obter a lista de fontes."</string>
|
||||
<string name="cant_create_source">"Não é possível criar fonte."</string>
|
||||
<string name="cant_get_spouts">"Não é possível obter a lista de spouts."</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"O formulário não está completo"</string>
|
||||
<string name="pref_header_links">"Links"</string>
|
||||
<string name="issue_tracker_link">"Rastreador de problemas"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Not connected !</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sync articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"Logar para adicionar fontes."</string>
|
||||
<string name="cant_get_sources">"Não é possível obter a lista de fontes."</string>
|
||||
<string name="cant_create_source">"Não é possível criar a fonte."</string>
|
||||
<string name="cant_get_spouts">"Não é possível obter a lista de bicos."</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"O formulário não está completo"</string>
|
||||
<string name="pref_header_links">"Links"</string>
|
||||
<string name="issue_tracker_link">"Rastreador de problemas"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Not connected !</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sync articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"Log in to add sources."</string>
|
||||
<string name="cant_get_sources">"Can't get sources list."</string>
|
||||
<string name="cant_create_source">"Can't create source."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list."</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"The form is not complete"</string>
|
||||
<string name="pref_header_links">"Links"</string>
|
||||
<string name="issue_tracker_link">"Issue Tracker"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Not connected !</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sync articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"Kaynakları eklemek için giriş yapın."</string>
|
||||
<string name="cant_get_sources">"Kaynakları listesi alınamıyor."</string>
|
||||
<string name="cant_create_source">"Kaynak oluşturulamıyor."</string>
|
||||
<string name="cant_get_spouts">"Spouts listesine girilemiyor."</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"Form tamamlanamadı"</string>
|
||||
<string name="pref_header_links">"Bağlantılar"</string>
|
||||
<string name="issue_tracker_link">"Sorun İzleyici"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Not connected !</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sync articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"登录以添加数据源。"</string>
|
||||
<string name="cant_get_sources">"无法获取数据列表。"</string>
|
||||
<string name="cant_create_source">"无法创建源数据。"</string>
|
||||
<string name="cant_get_spouts">"无法获取数据列表"</string>
|
||||
<string name="cant_get_spouts_no_network">"由于网络问题,无法获取 spouts 列表。"</string>
|
||||
<string name="cant_get_spouts">"无法获取 spouts 列表。可能有一个 api 问题。"</string>
|
||||
<string name="form_not_complete">"窗体未完成"</string>
|
||||
<string name="pref_header_links">"链接"</string>
|
||||
<string name="issue_tracker_link">"问题追踪器"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">检查新来源和标签</string>
|
||||
<string name="pref_switch_update_sources_summary">如果你的服务器接收过多的数据库查询,请禁用此功能。</string>
|
||||
<string name="no_network_connectivity">未连接!</string>
|
||||
<string name="network_connectivity_lost">"网络连接丢失"</string>
|
||||
<string name="network_connectivity_retrieved">"网络连接现在可用"</string>
|
||||
<string name="pref_switch_periodic_refresh">同步文章</string>
|
||||
<string name="pref_switch_periodic_refresh_off">文章将不会在后台同步</string>
|
||||
<string name="pref_switch_periodic_refresh_on">将定期同步文章</string>
|
||||
|
@ -40,7 +40,8 @@
|
||||
<string name="addStringNoUrl">"登录以添加数据源。"</string>
|
||||
<string name="cant_get_sources">"无法获取数据列表。"</string>
|
||||
<string name="cant_create_source">"无法创建源数据。"</string>
|
||||
<string name="cant_get_spouts">"无法获取数据列表"</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"窗体未完成"</string>
|
||||
<string name="pref_header_links">"链接"</string>
|
||||
<string name="issue_tracker_link">"问题追踪器"</string>
|
||||
@ -142,6 +143,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Not connected !</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sync articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||
|
@ -39,7 +39,8 @@
|
||||
<string name="addStringNoUrl">"Log in to add sources."</string>
|
||||
<string name="cant_get_sources">"Can't get sources list."</string>
|
||||
<string name="cant_create_source">"Can't create source."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list."</string>
|
||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
||||
<string name="form_not_complete">"The form is not complete"</string>
|
||||
<string name="pref_header_links">"Links"</string>
|
||||
<string name="issue_tracker_link">"Issue Tracker"</string>
|
||||
@ -143,6 +144,8 @@
|
||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
||||
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
||||
<string name="no_network_connectivity">Not connected !</string>
|
||||
<string name="network_connectivity_lost">"Network connection lost"</string>
|
||||
<string name="network_connectivity_retrieved">"Network connection is now available"</string>
|
||||
<string name="pref_switch_periodic_refresh">Sync articles</string>
|
||||
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
|
||||
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
|
||||
|
@ -6,10 +6,13 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
|
||||
classpath("com.android.tools.build:gradle:7.2.1")
|
||||
classpath("com.android.tools.build:gradle:7.2.2")
|
||||
|
||||
// sonarquve
|
||||
classpath("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513")
|
||||
|
||||
// SqlDelight
|
||||
classpath("com.squareup.sqldelight:gradle-plugin:1.5.3")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ redirect_from: "/ReaderforSelfoss-multiplatform/"
|
||||
|
||||
|
||||
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body id="main">
|
||||
@ -68,9 +68,9 @@ redirect_from: "/ReaderforSelfoss-multiplatform/"
|
||||
<div id="links">
|
||||
|
||||
|
||||
<a class="github-button" href="https://github.com/aminecmi/readerforselfoss-multiplatform" data-size="large" aria-label="Star aminecmi/readerforselfoss-multiplatform on GitHub">Star</a>
|
||||
<a class="github-button" href="https://gitea.amine-louveau.fr/Louvorg/readerforselfoss-multiplatform" data-size="large" aria-label="Star aminecmi/readerforselfoss-multiplatform on GitHub">Star</a>
|
||||
</div>
|
||||
<meta itemprop="url" content="https://github.com/aminecmi/readerforselfoss-multiplatform">
|
||||
<meta itemprop="url" content="https://gitea.amine-louveau.fr/Louvorg/readerforselfoss-multiplatform">
|
||||
<meta itemprop="applicationCategory" content="News & Magazines">
|
||||
</div>
|
||||
</body>
|
||||
|
@ -18,3 +18,7 @@ kotlin.native.enableDependencyPropagation=false
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
kotlin.mpp.enableGranularSourceSetsMetadata=true
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
ignoreGitVersion=false
|
||||
pushCache=true
|
||||
|
@ -1,3 +1,5 @@
|
||||
val pushCache: String by settings
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
@ -6,6 +8,16 @@ pluginManagement {
|
||||
}
|
||||
}
|
||||
|
||||
buildCache {
|
||||
remote<HttpBuildCache> {
|
||||
url = uri("http://18.0.0.7:3071/cache/")
|
||||
isAllowInsecureProtocol = true
|
||||
isAllowUntrustedServer = true
|
||||
isUseExpectContinue = true
|
||||
isPush = (pushCache == "true")
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "ReaderForSelfossV2"
|
||||
include(":androidApp")
|
||||
include(":shared")
|
@ -1,6 +1,14 @@
|
||||
object SqlDelight {
|
||||
const val runtime = "com.squareup.sqldelight:runtime:1.5.3"
|
||||
const val android = "com.squareup.sqldelight:android-driver:1.5.3"
|
||||
const val native = "com.squareup.sqldelight:native-driver:1.5.3"
|
||||
|
||||
}
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
id("com.android.library")
|
||||
id("com.squareup.sqldelight")
|
||||
kotlin("plugin.serialization") version "1.4.10"
|
||||
}
|
||||
|
||||
@ -36,6 +44,12 @@ kotlin {
|
||||
|
||||
//Logging
|
||||
implementation("io.github.aakira:napier:2.6.1")
|
||||
|
||||
// Network information
|
||||
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
|
||||
|
||||
// Sql
|
||||
implementation(SqlDelight.runtime)
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
@ -47,6 +61,9 @@ kotlin {
|
||||
val androidMain by getting {
|
||||
dependencies {
|
||||
implementation("io.ktor:ktor-client-android:2.0.1")
|
||||
|
||||
// Sql
|
||||
implementation(SqlDelight.android)
|
||||
}
|
||||
}
|
||||
val androidTest by getting {
|
||||
@ -63,6 +80,11 @@ kotlin {
|
||||
iosX64Main.dependsOn(this)
|
||||
iosArm64Main.dependsOn(this)
|
||||
//iosSimulatorArm64Main.dependsOn(this)
|
||||
|
||||
// Sql
|
||||
dependencies {
|
||||
implementation(SqlDelight.native)
|
||||
}
|
||||
}
|
||||
val iosX64Test by getting
|
||||
val iosArm64Test by getting
|
||||
@ -86,4 +108,16 @@ android {
|
||||
minSdk = 21
|
||||
targetSdk = 31
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
sqldelight {
|
||||
database("ReaderForSelfossDB") {
|
||||
packageName = "bou.amine.apps.readerforselfossv2.dao"
|
||||
sourceFolders = listOf("sqldelight")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2
|
||||
|
||||
actual class Platform actual constructor() {
|
||||
actual val platform: String = "Android ${android.os.Build.VERSION.SDK_INT}"
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package bou.amine.apps.readerforselfossv2.dao
|
||||
import android.content.Context
|
||||
import com.squareup.sqldelight.android.AndroidSqliteDriver
|
||||
import com.squareup.sqldelight.db.SqlDriver
|
||||
|
||||
actual class DriverFactory(private val context: Context) {
|
||||
actual fun createDriver(): SqlDriver {
|
||||
return AndroidSqliteDriver(ReaderForSelfossDB.Schema, context, "ReaderForSelfossV2-android.db")
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
actual class DateUtils actual constructor(private val apiMajorVersion: Int) {
|
||||
actual fun parseDate(dateString: String): Long {
|
||||
|
||||
val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss"
|
||||
|
||||
return if (apiMajorVersion >= 4) {
|
||||
OffsetDateTime.parse(dateString).toInstant().toEpochMilli()
|
||||
} else {
|
||||
LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant(
|
||||
ZoneOffset.UTC).toEpochMilli()
|
||||
}
|
||||
}
|
||||
|
||||
actual fun parseRelativeDate(dateString: String): String {
|
||||
|
||||
val date = parseDate(dateString)
|
||||
|
||||
return " " + DateUtils.getRelativeTimeSpanString(
|
||||
date,
|
||||
Instant.now().toEpochMilli(),
|
||||
DateUtils.MINUTE_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.Html
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import org.jsoup.Jsoup
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
actual fun String.getHtmlDecoded(): String {
|
||||
return Html.fromHtml(this).toString()
|
||||
}
|
||||
|
||||
actual fun SelfossModel.Item.getIcon(baseUrl: String): String {
|
||||
return constructUrl(baseUrl, "favicons", icon)
|
||||
}
|
||||
|
||||
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String {
|
||||
return constructUrl(baseUrl, "thumbnails", thumbnail)
|
||||
}
|
||||
|
||||
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
||||
val allImages = ArrayList<String>()
|
||||
|
||||
for ( image in Jsoup.parse(content).getElementsByTag("img")) {
|
||||
val url = image.attr("src")
|
||||
if (url.lowercase(Locale.US).contains(".jpg") ||
|
||||
url.lowercase(Locale.US).contains(".jpeg") ||
|
||||
url.lowercase(Locale.US).contains(".png") ||
|
||||
url.lowercase(Locale.US).contains(".webp"))
|
||||
{
|
||||
allImages.add(url)
|
||||
}
|
||||
}
|
||||
return allImages
|
||||
}
|
||||
|
||||
actual fun SelfossModel.Source.getIcon(baseUrl: String): String {
|
||||
return constructUrl(baseUrl, "favicons", icon)
|
||||
}
|
||||
|
||||
actual fun constructUrl(baseUrl: String, path: String, file: String?): String {
|
||||
return if (file == null || file == "null" || file.isEmpty()) {
|
||||
""
|
||||
} else {
|
||||
val baseUriBuilder = Uri.parse(baseUrl).buildUpon()
|
||||
baseUriBuilder.appendPath(path).appendPath(file)
|
||||
|
||||
baseUriBuilder.toString()
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2
|
||||
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class AndroidGreetingTest {
|
||||
|
||||
@Test
|
||||
fun testExample() {
|
||||
assertTrue("Check Android is mentioned", Greeting().greeting().contains("Android"))
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2
|
||||
|
||||
class Greeting {
|
||||
fun greeting(): String {
|
||||
return "Hello, ${Platform().platform}!"
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2
|
||||
|
||||
expect class Platform() {
|
||||
val platform: String
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
package bou.amine.apps.readerforselfossv2.dao
|
||||
|
||||
interface DeviceDatabase<ItemEntity> {
|
||||
suspend fun items(): List<ItemEntity>
|
||||
suspend fun insertAllItems(vararg items: ItemEntity)
|
||||
suspend fun deleteAllItems()
|
||||
suspend fun delete(item: ItemEntity)
|
||||
suspend fun updateItem(item: ItemEntity)
|
||||
}
|
||||
import com.squareup.sqldelight.db.SqlDriver
|
||||
|
||||
expect class DriverFactory {
|
||||
fun createDriver(): SqlDriver
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package bou.amine.apps.readerforselfossv2.model
|
||||
|
||||
class NetworkUnavailableException : Exception()
|
@ -1,7 +1,7 @@
|
||||
package bou.amine.apps.readerforselfossv2.rest
|
||||
package bou.amine.apps.readerforselfossv2.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import android.text.Html
|
||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
class SelfossModel {
|
||||
@ -11,11 +11,7 @@ class SelfossModel {
|
||||
val tag: String,
|
||||
val color: String,
|
||||
val unread: Int
|
||||
) {
|
||||
fun getTitleDecoded(): String {
|
||||
return Html.fromHtml(tag).toString()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class SuccessResponse(val success: Boolean) {
|
||||
@ -73,5 +69,40 @@ class SelfossModel {
|
||||
val link: String,
|
||||
val sourcetitle: String,
|
||||
val tags: List<String>
|
||||
)
|
||||
) {
|
||||
// TODO: maybe find a better way to handle these kind of urls
|
||||
fun getLinkDecoded(): String {
|
||||
var stringUrl: String
|
||||
stringUrl =
|
||||
if (link.startsWith("http://news.google.com/news/") || link.startsWith("https://news.google.com/news/")) {
|
||||
if (link.contains("&url=")) {
|
||||
link.substringAfter("&url=")
|
||||
} else {
|
||||
this.link.replace("&", "&")
|
||||
}
|
||||
} else {
|
||||
this.link.replace("&", "&")
|
||||
}
|
||||
|
||||
// handle :443 => https
|
||||
if (stringUrl.contains(":443")) {
|
||||
stringUrl = stringUrl.replace(":443", "").replace("http://", "https://")
|
||||
}
|
||||
|
||||
// handle url not starting with http
|
||||
if (stringUrl.startsWith("//")) {
|
||||
stringUrl = "http:$stringUrl"
|
||||
}
|
||||
|
||||
return stringUrl
|
||||
}
|
||||
|
||||
fun sourceAndDateText(dateUtils: DateUtils): String =
|
||||
this.sourcetitle.getHtmlDecoded() + dateUtils.parseRelativeDate(this.datetime)
|
||||
|
||||
fun toggleStar(): Item {
|
||||
this.starred = !this.starred
|
||||
return this
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +1,26 @@
|
||||
package bou.amine.apps.readerforselfossv2.repository
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.dao.*
|
||||
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||
import bou.amine.apps.readerforselfossv2.utils.*
|
||||
import com.github.ln_12.library.ConnectivityStatus
|
||||
import com.russhwolf.settings.Settings
|
||||
import io.github.aakira.napier.Napier
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService) {
|
||||
class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService, private val connectivityStatus: ConnectivityStatus, private val db: ReaderForSelfossDB) {
|
||||
val settings = Settings()
|
||||
|
||||
var items = ArrayList<SelfossModel.Item>()
|
||||
val isConnectionAvailable = connectivityStatus.isNetworkConnected
|
||||
var connectionMonitored = false
|
||||
|
||||
var baseUrl = apiDetails.getBaseUrl()
|
||||
lateinit var dateUtils: DateUtils
|
||||
@ -26,149 +32,232 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
var searchFilter: String? = null
|
||||
|
||||
var itemsCaching = settings.getBoolean("items_caching", false)
|
||||
var offlineOverride = false
|
||||
|
||||
var apiMajorVersion = 0
|
||||
var badgeUnread = 0
|
||||
set(value) {field = if (value < 0) { 0 } else { value } }
|
||||
var badgeAll = 0
|
||||
set(value) {field = if (value < 0) { 0 } else { value } }
|
||||
var badgeStarred = 0
|
||||
set(value) {field = if (value < 0) { 0 } else { value } }
|
||||
private val _badgeUnread = MutableStateFlow(0)
|
||||
val badgeUnread = _badgeUnread.asStateFlow()
|
||||
private val _badgeAll = MutableStateFlow(0)
|
||||
val badgeAll = _badgeAll.asStateFlow()
|
||||
private val _badgeStarred = MutableStateFlow(0)
|
||||
val badgeStarred = _badgeStarred.asStateFlow()
|
||||
|
||||
init {
|
||||
// TODO: Dispatchers.IO not available in KMM, an alternative solution should be found
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
updateApiVersion()
|
||||
dateUtils = DateUtils(apiMajorVersion)
|
||||
reloadBadges()
|
||||
isConnectionAvailable.collect { connectionAvailable ->
|
||||
if (connectionAvailable) {
|
||||
updateApiVersion()
|
||||
reloadBadges()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
||||
// TODO: Check connectivity, use the updatedSince parameter
|
||||
val fetchedItems = api.getItems(displayedItems.type,
|
||||
settings.getString("prefer_api_items_number", "200").toInt(),
|
||||
offset = 0,
|
||||
tagFilter?.tag,
|
||||
sourceFilter?.id?.toLong(),
|
||||
searchFilter,
|
||||
null)
|
||||
// TODO: Use the updatedSince parameter
|
||||
var fetchedItems: List<SelfossModel.Item>? = null
|
||||
if (isNetworkAvailable()) {
|
||||
fetchedItems = api.getItems(
|
||||
displayedItems.type,
|
||||
settings.getString("prefer_api_items_number", "200").toInt(),
|
||||
offset = 0,
|
||||
tagFilter?.tag,
|
||||
sourceFilter?.id?.toLong(),
|
||||
searchFilter,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
if (itemsCaching) {
|
||||
fetchedItems = getDBItems().filter {
|
||||
displayedItems == ItemType.ALL ||
|
||||
(it.unread && displayedItems == ItemType.UNREAD) ||
|
||||
(it.starred && displayedItems == ItemType.STARRED)
|
||||
}.map { it.toView() }
|
||||
}
|
||||
}
|
||||
|
||||
if (fetchedItems != null) {
|
||||
items = ArrayList(fetchedItems)
|
||||
sortItems()
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
|
||||
// TODO: Check connectivity
|
||||
val offset = items.size
|
||||
val fetchedItems = api.getItems(displayedItems.type,
|
||||
settings.getString("prefer_api_items_number", "200").toInt(),
|
||||
offset,
|
||||
tagFilter?.tag,
|
||||
sourceFilter?.id?.toLong(),
|
||||
searchFilter,
|
||||
null)
|
||||
var fetchedItems: List<SelfossModel.Item>? = null
|
||||
if (isNetworkAvailable()) {
|
||||
val offset = items.size
|
||||
fetchedItems = api.getItems(
|
||||
displayedItems.type,
|
||||
settings.getString("prefer_api_items_number", "200").toInt(),
|
||||
offset,
|
||||
tagFilter?.tag,
|
||||
sourceFilter?.id?.toLong(),
|
||||
searchFilter,
|
||||
null
|
||||
)
|
||||
} // When using the db cache, we load everything the first time, so there should be nothing more to load.
|
||||
|
||||
if (fetchedItems != null) {
|
||||
appendItems(fetchedItems)
|
||||
items.addAll(fetchedItems)
|
||||
sortItems()
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
suspend fun allItems(itemType: ItemType): List<SelfossModel.Item>? =
|
||||
api.getItems(itemType.type, 200, 0, tagFilter?.tag, sourceFilter?.id?.toLong(), searchFilter, null)
|
||||
|
||||
private fun appendItems(fetchedItems: List<SelfossModel.Item>) {
|
||||
// TODO: Store in DB if enabled by user
|
||||
val fetchedIDS = fetchedItems.map { it.id }
|
||||
val tmpItems = ArrayList(items.filterNot { it.id in fetchedIDS })
|
||||
tmpItems.addAll(fetchedItems)
|
||||
sortItems(tmpItems)
|
||||
items = tmpItems
|
||||
suspend fun getMaxItemsForBackground(itemType: ItemType): List<SelfossModel.Item>? {
|
||||
return if (isNetworkAvailable()) {
|
||||
api.getItems(
|
||||
itemType.type,
|
||||
200,
|
||||
0,
|
||||
tagFilter?.tag,
|
||||
sourceFilter?.id?.toLong(),
|
||||
searchFilter,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun sortItems(items: ArrayList<SelfossModel.Item>) {
|
||||
private fun sortItems() {
|
||||
items.sortByDescending { dateUtils.parseDate(it.datetime) }
|
||||
}
|
||||
|
||||
suspend fun reloadBadges(): Boolean {
|
||||
// TODO: Check connectivity, calculate from DB
|
||||
var success = false
|
||||
val response = api.stats()
|
||||
if (response != null) {
|
||||
badgeUnread = response.unread
|
||||
badgeAll = response.total
|
||||
badgeStarred = response.starred
|
||||
success = true
|
||||
if (isNetworkAvailable()) {
|
||||
val response = api.stats()
|
||||
if (response != null) {
|
||||
_badgeUnread.value = response.unread
|
||||
_badgeAll.value = response.total
|
||||
_badgeStarred.value = response.starred
|
||||
success = true
|
||||
}
|
||||
} else if (itemsCaching) {
|
||||
// TODO: do this differently, because it's not efficient
|
||||
val dbItems = getDBItems()
|
||||
_badgeUnread.value = dbItems.filter { item -> item.unread }.size
|
||||
_badgeStarred.value = dbItems.filter { item -> item.starred }.size
|
||||
_badgeAll.value = dbItems.size
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun getTags(): List<SelfossModel.Tag>? {
|
||||
// TODO: Check success, store in DB
|
||||
return api.tags()
|
||||
val tags = if (isNetworkAvailable()) {
|
||||
api.tags()
|
||||
} else {
|
||||
getDBTags().map { it.toView() }
|
||||
}
|
||||
if (tags != null) {
|
||||
resetDBTagsWithData(tags)
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
suspend fun getSpouts(): Map<String, SelfossModel.Spout>? {
|
||||
// TODO: Check success, store in DB
|
||||
return api.spouts()
|
||||
return if (isNetworkAvailable()) {
|
||||
api.spouts()
|
||||
} else {
|
||||
throw NetworkUnavailableException()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSources(): ArrayList<SelfossModel.Source>? {
|
||||
// TODO: Check success
|
||||
return api.sources()
|
||||
val sources = if (isNetworkAvailable()) {
|
||||
api.sources()
|
||||
} else {
|
||||
ArrayList(getDBSources().map { it.toView() })
|
||||
}
|
||||
if (sources != null) {
|
||||
resetDBSourcesWithData(sources)
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
suspend fun markAsRead(id: Int): Boolean {
|
||||
// TODO: Check internet connection
|
||||
val success = api.markAsRead(id.toString())?.isSuccess == true
|
||||
suspend fun markAsRead(item: SelfossModel.Item): Boolean {
|
||||
val success = markAsReadById(item.id)
|
||||
|
||||
if (success) {
|
||||
markAsReadLocally(items.first {it.id == id})
|
||||
markAsReadLocally(item)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun unmarkAsRead(id: Int): Boolean {
|
||||
// TODO: Check internet connection
|
||||
val success = api.unmarkAsRead(id.toString())?.isSuccess == true
|
||||
suspend fun markAsReadById(id: Int): Boolean {
|
||||
return if (isNetworkAvailable()) {
|
||||
api.markAsRead(id.toString())?.isSuccess == true
|
||||
} else {
|
||||
insertDBAction(id.toString(), read = true)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
|
||||
val success = unmarkAsReadById(item.id)
|
||||
|
||||
if (success) {
|
||||
unmarkAsReadLocally(items.first {it.id == id})
|
||||
unmarkAsReadLocally(item)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun starr(id: Int): Boolean {
|
||||
// TODO: Check success, store in DB
|
||||
val success = api.starr(id.toString())?.isSuccess == true
|
||||
suspend fun unmarkAsReadById(id: Int): Boolean {
|
||||
return if (isNetworkAvailable()) {
|
||||
api.unmarkAsRead(id.toString())?.isSuccess == true
|
||||
} else {
|
||||
insertDBAction(id.toString(), unread = true)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun starr(item: SelfossModel.Item): Boolean {
|
||||
val success = starrById(item.id)
|
||||
|
||||
if (success) {
|
||||
starrLocally(items.first {it.id == id})
|
||||
starrLocally(item)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun unstarr(id: Int): Boolean {
|
||||
// TODO: Check internet connection
|
||||
val success = api.unstarr(id.toString())?.isSuccess == true
|
||||
suspend fun starrById(id: Int): Boolean {
|
||||
return if (isNetworkAvailable()) {
|
||||
api.starr(id.toString())?.isSuccess == true
|
||||
} else {
|
||||
insertDBAction(id.toString(), starred = true)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun unstarr(item: SelfossModel.Item): Boolean {
|
||||
val success = unstarrById(item.id)
|
||||
|
||||
if (success) {
|
||||
unstarrLocally(items.first {it.id == id})
|
||||
unstarrLocally(item)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun markAllAsRead(ids: List<Int>): Boolean {
|
||||
// TODO: Check Internet connectivity, store in DB
|
||||
suspend fun unstarrById(id: Int): Boolean {
|
||||
return if (isNetworkAvailable()) {
|
||||
api.unstarr(id.toString())?.isSuccess == true
|
||||
} else {
|
||||
insertDBAction(id.toString(), starred = true)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
val success = api.markAllAsRead(ids.map { it.toString() })?.isSuccess == true
|
||||
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
||||
var success = false
|
||||
|
||||
if (success) {
|
||||
val itemsToMark = items.filter { it.id in ids }
|
||||
for (item in itemsToMark) {
|
||||
if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true) {
|
||||
success = true
|
||||
for (item in items) {
|
||||
markAsReadLocally(item)
|
||||
}
|
||||
}
|
||||
@ -176,34 +265,46 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
}
|
||||
|
||||
private fun markAsReadLocally(item: SelfossModel.Item) {
|
||||
// TODO: Mark also in the database
|
||||
if (item.unread) {
|
||||
item.unread = false
|
||||
badgeUnread -= 1
|
||||
_badgeUnread.value -= 1
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
updateDBItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun unmarkAsReadLocally(item: SelfossModel.Item) {
|
||||
// TODO: Mark also in the database
|
||||
if (!item.unread) {
|
||||
item.unread = true
|
||||
badgeUnread += 1
|
||||
_badgeUnread.value += 1
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
updateDBItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun starrLocally(item: SelfossModel.Item) {
|
||||
// TODO: Mark also in the database
|
||||
if (!item.starred) {
|
||||
item.starred = true
|
||||
badgeStarred += 1
|
||||
_badgeStarred.value += 1
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
updateDBItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun unstarrLocally(item: SelfossModel.Item) {
|
||||
// TODO: Mark also in the database
|
||||
if (item.starred) {
|
||||
item.starred = false
|
||||
badgeStarred -= 1
|
||||
_badgeStarred.value -= 1
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
updateDBItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,45 +315,53 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
tags: String,
|
||||
filter: String
|
||||
): Boolean {
|
||||
// TODO: Check connectivity
|
||||
val response = api.createSourceForVersion(
|
||||
title,
|
||||
url,
|
||||
spout,
|
||||
tags,
|
||||
filter,
|
||||
apiMajorVersion
|
||||
)
|
||||
var response = false
|
||||
if (isNetworkAvailable()) {
|
||||
response = api.createSourceForVersion(
|
||||
title,
|
||||
url,
|
||||
spout,
|
||||
tags,
|
||||
filter,
|
||||
apiMajorVersion
|
||||
)?.isSuccess == true
|
||||
}
|
||||
|
||||
return response != null
|
||||
return response
|
||||
}
|
||||
|
||||
suspend fun deleteSource(id: Int): Boolean {
|
||||
// TODO: Check connectivity, store in DB
|
||||
var success = false
|
||||
val response = api.deleteSource(id)
|
||||
if (response != null) {
|
||||
success = response.isSuccess
|
||||
if (isNetworkAvailable()) {
|
||||
val response = api.deleteSource(id)
|
||||
if (response != null) {
|
||||
success = response.isSuccess
|
||||
}
|
||||
}
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun updateRemote(): Boolean {
|
||||
// TODO: Handle connectivity issues
|
||||
val response = api.update()
|
||||
return response?.isSuccess ?: false
|
||||
return if (isNetworkAvailable()) {
|
||||
api.update()?.equals("finished") ?: false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun login(): Boolean {
|
||||
var result = false
|
||||
try {
|
||||
val response = api.login()
|
||||
if (response != null && response.isSuccess) {
|
||||
result = true
|
||||
if (isNetworkAvailable()) {
|
||||
try {
|
||||
val response = api.login()
|
||||
result = response?.isSuccess == true
|
||||
if (result) {
|
||||
updateApiVersion()
|
||||
}
|
||||
} catch (cause: Throwable) {
|
||||
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.updateRemote")
|
||||
}
|
||||
} catch (cause: Throwable) {
|
||||
Napier.e(cause.stackTraceToString(),tag = "RepositoryImpl.updateRemote")
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -271,15 +380,110 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
||||
}
|
||||
|
||||
private suspend fun updateApiVersion() {
|
||||
// TODO: Handle connectivity issues
|
||||
val fetchedVersion = api.version()
|
||||
if (fetchedVersion != null) {
|
||||
apiMajorVersion = fetchedVersion.getApiMajorVersion()
|
||||
settings.putInt("apiVersionMajor", apiMajorVersion)
|
||||
} else {
|
||||
apiMajorVersion = settings.getInt("apiVersionMajor", 0)
|
||||
apiMajorVersion = settings.getInt("apiVersionMajor", 0)
|
||||
|
||||
if (isNetworkAvailable()) {
|
||||
val fetchedVersion = api.version()
|
||||
if (fetchedVersion != null) {
|
||||
apiMajorVersion = fetchedVersion.getApiMajorVersion()
|
||||
settings.putInt("apiVersionMajor", apiMajorVersion)
|
||||
}
|
||||
}
|
||||
dateUtils = DateUtils(apiMajorVersion)
|
||||
}
|
||||
|
||||
fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride
|
||||
|
||||
fun getDBActions(): List<ACTION> =
|
||||
db.actionsQueries.actions().executeAsList()
|
||||
|
||||
fun deleteDBAction(action: ACTION) =
|
||||
db.actionsQueries.deleteAction(action.id)
|
||||
|
||||
fun getDBTags(): List<TAG> = db.tagsQueries.tags().executeAsList()
|
||||
|
||||
fun getDBSources(): List<SOURCE> = db.sourcesQueries.sources().executeAsList()
|
||||
|
||||
fun resetDBTagsWithData(tagEntities: List<SelfossModel.Tag>) {
|
||||
db.tagsQueries.deleteAllTags()
|
||||
|
||||
db.tagsQueries.transaction {
|
||||
tagEntities.forEach { tag ->
|
||||
db.tagsQueries.insertTag(tag.toEntity())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Handle offline actions
|
||||
fun resetDBSourcesWithData(sources: List<SelfossModel.Source>) {
|
||||
db.sourcesQueries.deleteAllSources()
|
||||
|
||||
db.sourcesQueries.transaction {
|
||||
sources.forEach { source ->
|
||||
db.sourcesQueries.insertSource(source.toEntity())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertDBItems(items: List<SelfossModel.Item>) {
|
||||
db.itemsQueries.transaction {
|
||||
items.forEach { item ->
|
||||
db.itemsQueries.insertItem(item.toEntity())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDBItems(): List<ITEM> = db.itemsQueries.items().executeAsList()
|
||||
|
||||
private fun insertDBAction(articleid: String, read: Boolean = false, unread: Boolean = false, starred: Boolean = false, unstarred: Boolean = false) =
|
||||
db.actionsQueries.insertAction(articleid, read, unread, starred, unstarred)
|
||||
|
||||
private fun updateDBItem(item: SelfossModel.Item) =
|
||||
db.itemsQueries.updateItem(item.datetime, item.title.getHtmlDecoded(), item.content, item.unread, item.starred, item.thumbnail, item.icon, item.link, item.sourcetitle, item.tags.joinToString(","), item.id.toString())
|
||||
|
||||
|
||||
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item>? {
|
||||
if (itemsCaching) {
|
||||
try {
|
||||
val newItems = getMaxItemsForBackground(ItemType.UNREAD)
|
||||
val allItems = getMaxItemsForBackground(ItemType.ALL)
|
||||
val starredItems = getMaxItemsForBackground(ItemType.STARRED)
|
||||
insertDBItems(newItems.orEmpty() + allItems.orEmpty() + starredItems.orEmpty())
|
||||
return newItems
|
||||
} catch (e: Throwable) {
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
suspend fun handleDBActions() {
|
||||
|
||||
val actions: List<ACTION> = getDBActions()
|
||||
|
||||
actions.forEach { action ->
|
||||
when {
|
||||
action.read -> doAndReportOnFail(
|
||||
markAsReadById(action.articleid.toInt()),
|
||||
action
|
||||
)
|
||||
action.unread -> doAndReportOnFail(
|
||||
unmarkAsReadById(action.articleid.toInt()),
|
||||
action
|
||||
)
|
||||
action.starred -> doAndReportOnFail(
|
||||
starrById(action.articleid.toInt()),
|
||||
action
|
||||
)
|
||||
action.unstarred -> doAndReportOnFail(
|
||||
unstarrById(action.articleid.toInt()),
|
||||
action
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun doAndReportOnFail(result: Boolean, action: ACTION) {
|
||||
if (result) {
|
||||
deleteDBAction(action)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package bou.amine.apps.readerforselfossv2.rest
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
@ -99,7 +100,7 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) {
|
||||
parameter("password", apiDetailsService.getPassword())
|
||||
}.body()
|
||||
|
||||
suspend fun update(): SelfossModel.SuccessResponse? =
|
||||
suspend fun update(): String? =
|
||||
client.get(url("/update")) {
|
||||
parameter("username", apiDetailsService.getUserName())
|
||||
parameter("password", apiDetailsService.getPassword())
|
||||
|
@ -1,34 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.service
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.dao.DeviceDatabase
|
||||
import bou.amine.apps.readerforselfossv2.utils.parseDate
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
|
||||
abstract class DeviceDataBaseService<ItemEntity>(val db: DeviceDatabase<ItemEntity>, private val searchService: SearchService) {
|
||||
var itemsCaching = false
|
||||
var items: ArrayList<SelfossModel.Item> = arrayListOf()
|
||||
get() {
|
||||
return ArrayList(field)
|
||||
}
|
||||
set(value) {
|
||||
field = ArrayList(value)
|
||||
}
|
||||
|
||||
abstract suspend fun updateDatabase()
|
||||
abstract suspend fun clearDBItems()
|
||||
abstract fun appendNewItems(items: List<SelfossModel.Item>)
|
||||
abstract fun getFromDB()
|
||||
|
||||
fun sortItems() {
|
||||
val tmpItems = ArrayList(items.sortedByDescending { it.parseDate(searchService.dateUtils) })
|
||||
items = tmpItems
|
||||
}
|
||||
|
||||
// This filtered items from items val. Do not use
|
||||
fun getFocusedItems() {}
|
||||
fun computeBadges() {
|
||||
searchService.badgeUnread = items.filter { item -> item.unread }.size
|
||||
searchService.badgeStarred = items.filter { item -> item.starred }.size
|
||||
searchService.badgeAll = items.size
|
||||
}
|
||||
}
|
@ -1,42 +1,13 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
//import android.text.format.DateUtils
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
|
||||
fun SelfossModel.Item.parseDate(dateUtils: bou.amine.apps.readerforselfossv2.utils.DateUtils): Instant =
|
||||
|
||||
fun SelfossModel.Item.parseDate(dateUtils: DateUtils): Long =
|
||||
dateUtils.parseDate(this.datetime)
|
||||
|
||||
fun SelfossModel.Item.parseRelativeDate(dateUtils: bou.amine.apps.readerforselfossv2.utils.DateUtils): String =
|
||||
dateUtils.parseRelativeDate(this.datetime)
|
||||
expect class DateUtils(apiMajorVersion: Int) {
|
||||
fun parseDate(dateString: String): Long
|
||||
|
||||
class DateUtils(private val apiMajorVersion: Int) {
|
||||
fun parseDate(dateString: String): Instant {
|
||||
|
||||
val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss"
|
||||
|
||||
return if (apiMajorVersion >= 4) {
|
||||
OffsetDateTime.parse(dateString).toInstant()
|
||||
} else {
|
||||
LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant(ZoneOffset.UTC)
|
||||
}
|
||||
}
|
||||
|
||||
fun parseRelativeDate(dateString: String): String {
|
||||
|
||||
val date = parseDate(dateString)
|
||||
|
||||
// TODO:
|
||||
// return " " + DateUtils.getRelativeTimeSpanString(
|
||||
// date.toEpochMilli(),
|
||||
// Instant.now().toEpochMilli(),
|
||||
// 60000L, // DateUtils.MINUTE_IN_MILLIS,
|
||||
// 262144 // DateUtils.FORMAT_ABBREV_RELATIVE
|
||||
// )
|
||||
return dateString
|
||||
}
|
||||
}
|
||||
fun parseRelativeDate(dateString: String): String
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.dao.ITEM
|
||||
import bou.amine.apps.readerforselfossv2.dao.SOURCE
|
||||
import bou.amine.apps.readerforselfossv2.dao.TAG
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
|
||||
fun TAG.toView(): SelfossModel.Tag =
|
||||
SelfossModel.Tag(
|
||||
this.name,
|
||||
this.color,
|
||||
this.unread.toInt()
|
||||
)
|
||||
|
||||
fun SOURCE.toView(): SelfossModel.Source =
|
||||
SelfossModel.Source(
|
||||
this.id.toInt(),
|
||||
this.title,
|
||||
this.tags.split(","),
|
||||
this.spout,
|
||||
this.error,
|
||||
this.icon
|
||||
)
|
||||
|
||||
fun SelfossModel.Source.toEntity(): SOURCE =
|
||||
SOURCE(
|
||||
this.id.toString(),
|
||||
this.title.getHtmlDecoded(),
|
||||
this.tags.joinToString(","),
|
||||
this.spout,
|
||||
this.error,
|
||||
this.icon.orEmpty()
|
||||
)
|
||||
|
||||
fun SelfossModel.Tag.toEntity(): TAG =
|
||||
TAG(
|
||||
this.tag,
|
||||
this.color,
|
||||
this.unread.toLong()
|
||||
)
|
||||
|
||||
fun ITEM.toView(): SelfossModel.Item =
|
||||
SelfossModel.Item(
|
||||
this.id.toInt(),
|
||||
this.datetime,
|
||||
this.title,
|
||||
this.content,
|
||||
this.unread,
|
||||
this.starred,
|
||||
this.thumbnail,
|
||||
this.icon,
|
||||
this.link,
|
||||
this.sourcetitle,
|
||||
this.tags.split(",")
|
||||
)
|
||||
|
||||
fun SelfossModel.Item.toEntity(): ITEM =
|
||||
ITEM(
|
||||
this.id.toString(),
|
||||
this.datetime,
|
||||
this.title.getHtmlDecoded(),
|
||||
this.content,
|
||||
this.unread,
|
||||
this.starred,
|
||||
this.thumbnail,
|
||||
this.icon,
|
||||
this.link,
|
||||
this.title.getHtmlDecoded(),
|
||||
this.tags.joinToString(",")
|
||||
)
|
@ -0,0 +1,15 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
|
||||
expect fun String.getHtmlDecoded(): String
|
||||
|
||||
expect fun SelfossModel.Item.getIcon(baseUrl: String): String
|
||||
|
||||
expect fun SelfossModel.Item.getThumbnail(baseUrl: String): String
|
||||
|
||||
expect fun SelfossModel.Item.getImages(): ArrayList<String>
|
||||
|
||||
expect fun SelfossModel.Source.getIcon(baseUrl: String): String
|
||||
|
||||
expect fun constructUrl(baseUrl: String, path: String, file: String?): String
|
@ -0,0 +1,18 @@
|
||||
CREATE TABLE `ACTION` (
|
||||
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`articleid` TEXT NOT NULL,
|
||||
`read` INTEGER AS Boolean DEFAULT 0 NOT NULL,
|
||||
`unread` INTEGER AS Boolean DEFAULT 0 NOT NULL,
|
||||
`starred` INTEGER AS Boolean DEFAULT 0 NOT NULL,
|
||||
`unstarred` INTEGER AS Boolean DEFAULT 0 NOT NULL
|
||||
);
|
||||
|
||||
actions:
|
||||
SELECT *
|
||||
FROM `ACTION`;
|
||||
|
||||
insertAction:
|
||||
INSERT OR REPLACE INTO `ACTION` (`articleid`, `read`, `unread`, `starred`, `unstarred`) VALUES (?, ?, ?, ?, ?);
|
||||
|
||||
deleteAction:
|
||||
DELETE FROM `ACTION` WHERE id = ?;
|
@ -0,0 +1,30 @@
|
||||
CREATE TABLE ITEM (
|
||||
`id` TEXT NOT NULL,
|
||||
`datetime` TEXT NOT NULL,
|
||||
`title` TEXT NOT NULL,
|
||||
`content` TEXT NOT NULL,
|
||||
`unread` INTEGER AS Boolean DEFAULT 0 NOT NULL,
|
||||
`starred` INTEGER AS Boolean DEFAULT 0 NOT NULL,
|
||||
`thumbnail` TEXT,
|
||||
`icon` TEXT,
|
||||
`link` TEXT NOT NULL,
|
||||
`sourcetitle` TEXT NOT NULL,
|
||||
`tags` TEXT NOT NULL,
|
||||
PRIMARY KEY(`id`)
|
||||
);
|
||||
|
||||
CREATE INDEX item_title ON ITEM(`title`);
|
||||
CREATE INDEX item_source ON ITEM(`sourcetitle`);
|
||||
|
||||
|
||||
items:
|
||||
SELECT * FROM ITEM ORDER BY `id` DESC;
|
||||
|
||||
insertItem:
|
||||
INSERT OR REPLACE INTO ITEM VALUES ?;
|
||||
|
||||
deleteItem:
|
||||
DELETE FROM ITEM WHERE `id` = ?;
|
||||
|
||||
updateItem:
|
||||
UPDATE ITEM SET `datetime` = ?, `title` = ?, `content` = ?, `unread` = ?, `starred` = ?, `thumbnail` = ?, `icon` = ?, `link` = ?, `sourcetitle` = ?, `tags` = ? WHERE `id` = ?;
|
@ -0,0 +1,21 @@
|
||||
CREATE TABLE SOURCE (
|
||||
`id` TEXT NOT NULL,
|
||||
`title` TEXT NOT NULL,
|
||||
`tags` TEXT NOT NULL,
|
||||
`spout` TEXT NOT NULL,
|
||||
`error` TEXT NOT NULL,
|
||||
`icon` TEXT NOT NULL,
|
||||
PRIMARY KEY(`id`)
|
||||
);
|
||||
|
||||
sources:
|
||||
SELECT * FROM SOURCE;
|
||||
|
||||
insertSource:
|
||||
INSERT OR REPLACE INTO SOURCE VALUES ?;
|
||||
|
||||
deleteAllSources:
|
||||
DELETE FROM SOURCE;
|
||||
|
||||
deleteSource:
|
||||
DELETE FROM SOURCE WHERE `id` = ?;
|
@ -0,0 +1,20 @@
|
||||
CREATE TABLE TAG (
|
||||
`name` TEXT NOT NULL,
|
||||
`color` TEXT NOT NULL,
|
||||
`unread` INTEGER NOT NULL,
|
||||
PRIMARY KEY(`name`)
|
||||
);
|
||||
|
||||
CREATE INDEX tag_name ON TAG(`name`);
|
||||
|
||||
tags:
|
||||
SELECT * FROM TAG;
|
||||
|
||||
insertTag:
|
||||
INSERT OR REPLACE INTO TAG VALUES ?;
|
||||
|
||||
deleteAllTags:
|
||||
DELETE FROM TAG;
|
||||
|
||||
deleteTag:
|
||||
DELETE FROM TAG WHERE `name` = ? AND `color` = ?;
|
@ -1,12 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class CommonGreetingTest {
|
||||
|
||||
@Test
|
||||
fun testExample() {
|
||||
assertTrue(Greeting().greeting().contains("Hello"), "Check 'Hello' is mentioned")
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package bou.amine.apps.readerforselfossv2.dao
|
||||
|
||||
import com.squareup.sqldelight.db.SqlDriver
|
||||
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
|
||||
|
||||
actual class DriverFactory {
|
||||
actual fun createDriver(): SqlDriver {
|
||||
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
actual class DateUtils actual constructor(apiMajorVersion: Int) {
|
||||
actual fun parseDate(dateString: String): Long {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun parseRelativeDate(dateString: String): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
|
||||
actual fun String.getHtmlDecoded(): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun SelfossModel.Item.getIcon(baseUrl: String): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun SelfossModel.Source.getIcon(baseUrl: String): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun constructUrl(baseUrl: String, path: String, file: String?): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2
|
||||
|
||||
import platform.UIKit.UIDevice
|
||||
|
||||
actual class Platform actual constructor() {
|
||||
actual val platform: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package bou.amine.apps.readerforselfossv2.dao
|
||||
|
||||
import com.squareup.sqldelight.db.SqlDriver
|
||||
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
|
||||
|
||||
actual class DriverFactory {
|
||||
actual fun createDriver(): SqlDriver {
|
||||
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
actual class DateUtils actual constructor(apiMajorVersion: Int) {
|
||||
actual fun parseDate(dateString: String): Long {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun parseRelativeDate(dateString: String): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
|
||||
actual fun String.getHtmlDecoded(): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun SelfossModel.Item.getIcon(baseUrl: String): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun SelfossModel.Source.getIcon(baseUrl: String): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun constructUrl(baseUrl: String, path: String, file: String?): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
Reference in New Issue
Block a user