Compare commits
2 Commits
v125030711
...
6b5f6cbbe0
Author | SHA1 | Date | |
---|---|---|---|
6b5f6cbbe0 | |||
5035392aff |
@@ -3,34 +3,25 @@ root = true
|
|||||||
[*]
|
[*]
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[.editorconfig]
|
[{*.kt,*.kts}]
|
||||||
insert_final_newline = false
|
|
||||||
ij_kotlin_line_break_after_multiline_when_entry = false
|
|
||||||
|
|
||||||
[*.{kt,kts}]
|
[*.{kt,kts}]
|
||||||
# Disable wildcard imports entirely
|
# Disable wildcard imports entirely
|
||||||
ij_kotlin_name_count_to_use_star_import = 2147483647
|
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||||
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||||
|
|
||||||
|
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
ij_kotlin_allow_trailing_comma = true
|
ij_kotlin_allow_trailing_comma = true
|
||||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||||
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
|
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
|
||||||
ij_kotlin_indent_before_arrow_on_new_line = false
|
|
||||||
ij_kotlin_line_break_after_multiline_when_entry = true
|
|
||||||
ij_kotlin_packages_to_use_import_on_demand = unset
|
ij_kotlin_packages_to_use_import_on_demand = unset
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
indent_style = space
|
indent_style = space
|
||||||
ktlint_argument_list_wrapping_ignore_when_parameter_count_greater_or_equal_than = unset
|
ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than = unset
|
||||||
ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than = 4
|
|
||||||
ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 1
|
ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 1
|
||||||
ktlint_code_style = ktlint_official
|
ktlint_code_style = ktlint_official
|
||||||
ktlint_enum_entry_name_casing = upper_or_camel_cases
|
|
||||||
ktlint_function_naming_ignore_when_annotated_with = unset
|
|
||||||
ktlint_function_signature_body_expression_wrapping = multiline
|
ktlint_function_signature_body_expression_wrapping = multiline
|
||||||
ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 2
|
ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 2
|
||||||
ktlint_ignore_back_ticked_identifier = false
|
ktlint_ignore_back_ticked_identifier = false
|
||||||
ktlint_property_naming_constant_naming = screaming_snake_case
|
max_line_length = 140
|
||||||
max_line_length = 140
|
|
||||||
|
|
||||||
[**/build]
|
|
||||||
ktlint = disabled
|
|
@@ -1,10 +0,0 @@
|
|||||||
project_id_env: CROWDIN_PROJECT_ID
|
|
||||||
api_token_env: CROWDIN_PERSONAL_TOKEN
|
|
||||||
base_path: "../../../"
|
|
||||||
|
|
||||||
files:
|
|
||||||
- source: /androidApp/src/main/res/values/strings.xml
|
|
||||||
translation: /androidApp/src/main/res/values-%android_code%/%original_file_name%
|
|
||||||
translate_attributes: '0'
|
|
||||||
content_segmentation: '0'
|
|
||||||
preserve_hierarchy: true
|
|
@@ -10,52 +10,36 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: "Check android app changes"
|
|
||||||
id: check-android-changes
|
|
||||||
uses: tj-actions/changed-files@v45
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
androidApp/src/**
|
|
||||||
- name: Fetch tags
|
- name: Fetch tags
|
||||||
if: steps.check-android-changes.outputs.any_modified == 'true'
|
|
||||||
run: git fetch --tags -p
|
run: git fetch --tags -p
|
||||||
- uses: actions/setup-java@v4
|
- uses: actions/setup-java@v4
|
||||||
if: steps.check-android-changes.outputs.any_modified == 'true'
|
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
cache: gradle
|
cache: gradle
|
||||||
- uses: gradle/actions/setup-gradle@v3
|
- uses: gradle/actions/setup-gradle@v3
|
||||||
if: steps.check-android-changes.outputs.any_modified == 'true'
|
|
||||||
- uses: android-actions/setup-android@v3
|
- uses: android-actions/setup-android@v3
|
||||||
if: steps.check-android-changes.outputs.any_modified == 'true'
|
|
||||||
- name: Configure gradle...
|
- name: Configure gradle...
|
||||||
if: steps.check-android-changes.outputs.any_modified == 'true'
|
|
||||||
run: mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true" >> ~/.gradle/gradle.properties
|
run: mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true" >> ~/.gradle/gradle.properties
|
||||||
- name: Build and test
|
- name: Build and test
|
||||||
if: steps.check-android-changes.outputs.any_modified == 'true'
|
|
||||||
run: ./gradlew build -x testReleaseUnitTest -x testDebugUnitTest -x testGithubConfigReleaseUnitTest -x testGithubConfigDebugUnitTest # These tests will be done
|
run: ./gradlew build -x testReleaseUnitTest -x testDebugUnitTest -x testGithubConfigReleaseUnitTest -x testGithubConfigDebugUnitTest # These tests will be done
|
||||||
# TESTS ARE RUN LOCALLY
|
- uses: KengoTODA/actions-setup-docker-compose@v1
|
||||||
# - uses: KengoTODA/actions-setup-docker-compose@v1
|
with:
|
||||||
# with:
|
version: "2.23.3"
|
||||||
# version: "2.23.3"
|
- name: run selfoss
|
||||||
# - name: run selfoss
|
run: |
|
||||||
# run: |
|
docker compose -f .gitea/workflows/assets/docker-compose.yml up -d
|
||||||
# docker compose -f .gitea/workflows/assets/docker-compose.yml up -d
|
|
||||||
- name: coverage
|
- name: coverage
|
||||||
if: steps.check-android-changes.outputs.any_modified == 'true'
|
|
||||||
run: |
|
run: |
|
||||||
./gradlew :koverHtmlReport
|
./gradlew :koverHtmlReport
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
if: steps.check-android-changes.outputs.any_modified == 'true'
|
|
||||||
with:
|
with:
|
||||||
name: coverage
|
name: coverage
|
||||||
path: build/reports/kover/html
|
path: build/reports/kover/html
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
overwrite: true
|
overwrite: true
|
||||||
include-hidden-files: true
|
include-hidden-files: true
|
||||||
# TESTS ARE RUN LOCALLY
|
- name: Clean
|
||||||
# - name: Clean
|
if: always()
|
||||||
# if: always()
|
run: |
|
||||||
# run: |
|
docker compose -f .gitea/workflows/assets/docker-compose.yml stop
|
||||||
# docker compose -f .gitea/workflows/assets/docker-compose.yml stop
|
|
@@ -16,7 +16,6 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: master
|
|
||||||
- name: Config git
|
- name: Config git
|
||||||
run: |
|
run: |
|
||||||
git config --global user.email aminecmi+giteadrone@pm.me
|
git config --global user.email aminecmi+giteadrone@pm.me
|
||||||
@@ -51,7 +50,7 @@ jobs:
|
|||||||
followtags: true
|
followtags: true
|
||||||
ssh_key: ${{ secrets.PRIVATE_KEY }}
|
ssh_key: ${{ secrets.PRIVATE_KEY }}
|
||||||
tags: true
|
tags: true
|
||||||
branch: master
|
branch: release
|
||||||
- name: copy file via ssh password
|
- name: copy file via ssh password
|
||||||
uses: appleboy/scp-action@v0.1.7
|
uses: appleboy/scp-action@v0.1.7
|
||||||
with:
|
with:
|
||||||
@@ -125,4 +124,4 @@ jobs:
|
|||||||
priority: high
|
priority: high
|
||||||
convert_markdown: true
|
convert_markdown: true
|
||||||
body: Nouveau fichier de mapping pour la version ${{ steps.version.outputs.VERSION }}
|
body: Nouveau fichier de mapping pour la version ${{ steps.version.outputs.VERSION }}
|
||||||
attachments: androidApp/build/outputs/mapping/githubConfigRelease/mapping.txt
|
attachments: androidApp/build/outputs/mapping/githubConfigRelease/mapping.txt
|
@@ -3,7 +3,6 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- chore-crowdin-ci
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Lint:
|
Lint:
|
||||||
@@ -17,75 +16,13 @@ jobs:
|
|||||||
java-version: '17'
|
java-version: '17'
|
||||||
cache: gradle
|
cache: gradle
|
||||||
- name: Install klint
|
- name: Install klint
|
||||||
run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.5.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
|
run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.0.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
|
||||||
- name: Install detekt
|
- name: Install detekt
|
||||||
run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.7/detekt-cli-1.23.7.zip && unzip detekt-cli-1.23.7.zip
|
run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.1/detekt-cli-1.23.1.zip && unzip detekt-cli-1.23.1.zip
|
||||||
- name: Linting...
|
- name: Linting...
|
||||||
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build'
|
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build'
|
||||||
- name: Detecting...
|
- name: Detecting...
|
||||||
run: ./detekt-cli-1.23.7/bin/detekt-cli -c detekt.yml --excludes '**/shared/build/**/*.kt'
|
run: ./detekt-cli-1.23.1/bin/detekt-cli --all-rules --excludes '**/shared/build/**/*.kt' || true
|
||||||
translations:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out repository code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: "Check translations changes"
|
|
||||||
id: check-translations-changes
|
|
||||||
uses: tj-actions/changed-files@v45
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
androidApp/src/main/res/values/strings.xml
|
|
||||||
- name: upload translation sources
|
|
||||||
if: steps.check-api-changes.outputs.any_modified == 'true'
|
|
||||||
uses: crowdin/github-action@v2
|
|
||||||
with:
|
|
||||||
config: './.gitea/workflows/assets/crowdin.yml'
|
|
||||||
upload_sources: true
|
|
||||||
upload_translations: false
|
|
||||||
download_translations: false
|
|
||||||
create_pull_request: false
|
|
||||||
push_translations: false
|
|
||||||
env:
|
|
||||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
|
||||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
|
||||||
- name: wait
|
|
||||||
if: steps.check-api-changes.outputs.any_modified == 'true'
|
|
||||||
run: sleep 10s
|
|
||||||
- name: download translations
|
|
||||||
if: steps.check-api-changes.outputs.any_modified == 'true'
|
|
||||||
uses: crowdin/github-action@v2
|
|
||||||
with:
|
|
||||||
config: './.gitea/workflows/assets/crowdin.yml'
|
|
||||||
upload_sources: false
|
|
||||||
upload_translations: false
|
|
||||||
download_translations: true
|
|
||||||
create_pull_request: false
|
|
||||||
push_translations: false
|
|
||||||
env:
|
|
||||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
|
||||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
|
||||||
- name: Check for uncommitted changes
|
|
||||||
if: steps.check-api-changes.outputs.any_modified == 'true'
|
|
||||||
id: check-changes
|
|
||||||
uses: mskri/check-uncommitted-changes-action@v1.0.1
|
|
||||||
- name: Commit Changes
|
|
||||||
if: steps.check-api-changes.outputs.any_modified == 'true' && steps.check-changes.outputs.changes != ''
|
|
||||||
run: |
|
|
||||||
git config --global user.email aminecmi+giteadrone@pm.me
|
|
||||||
git config --global user.name giteadrone
|
|
||||||
git add ./androidApp/src/main/res/*
|
|
||||||
git commit -m "translation: translation files"
|
|
||||||
- name: Push changes
|
|
||||||
if: steps.check-api-changes.outputs.any_modified == 'true' && steps.check-changes.outputs.changes != ''
|
|
||||||
uses: appleboy/git-push-action@v1.0.0
|
|
||||||
with:
|
|
||||||
author_name: giteadrone
|
|
||||||
author_email: aminecmi+giteadrone@pm.me
|
|
||||||
remote: ${{ secrets.REMOTE_URL }}
|
|
||||||
ssh_key: ${{ secrets.PRIVATE_KEY }}
|
|
||||||
branch: ${{ github.head_ref || github.ref_name }}
|
|
||||||
build:
|
build:
|
||||||
needs: Lint
|
needs: Lint
|
||||||
uses: ./.gitea/workflows/common_build.yml
|
uses: ./.gitea/workflows/common_build.yml
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -323,6 +323,4 @@ fabric.properties
|
|||||||
crowdin.properties
|
crowdin.properties
|
||||||
|
|
||||||
.kotlin/
|
.kotlin/
|
||||||
build-cache/
|
build-cache/
|
||||||
|
|
||||||
act
|
|
77
CHANGELOG.md
77
CHANGELOG.md
@@ -1,80 +1,3 @@
|
|||||||
**v125030681
|
|
||||||
|
|
||||||
- chore: do not send reports on simulators.
|
|
||||||
- Merge pull request 'chore: do not send reports on simulators.' (#188) from chore-acra-simulator into master
|
|
||||||
- chore: do not send reports on simulators.
|
|
||||||
- Merge pull request 'fix: Url validation was not failing login. Added tests.' (#186) from fix-invalid-url into master
|
|
||||||
- Merge pull request 'chore: crowding ci integration.' (#187) from chore-crowdin-ci into master
|
|
||||||
- chore: we don't need to check if the url is valid in upsert screen.
|
|
||||||
- fix: Url validation was not failing login. Added tests.
|
|
||||||
- chore: crowding ci integration.
|
|
||||||
- Show a confirmation dialog before deleting sources (#185)
|
|
||||||
- Changelog for v125020581
|
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
**v125020581
|
|
||||||
|
|
||||||
- fix: url can be empty ?
|
|
||||||
- Changelog for v125020471
|
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
**v125020471
|
|
||||||
|
|
||||||
- chore: no more docker-compose.
|
|
||||||
- bump: gradle plugin.
|
|
||||||
- Merge pull request 'fix: check index exists.' (#183) from fix-index into master
|
|
||||||
- fix: check index exists.
|
|
||||||
- Changelog for v125020411
|
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
**v125020411
|
|
||||||
|
|
||||||
- Merge pull request 'bump' (#182) from bump into master
|
|
||||||
- chore: non transiant R classes.
|
|
||||||
- Merge pull request 'fix: One more missing context.' (#181) from fix-one-more-context into master
|
|
||||||
- bump
|
|
||||||
- fix: One more missing context.
|
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
**v125010241
|
|
||||||
|
|
||||||
- Merge pull request 'fix: Link not opening.' (#178) from fix-open-link into master
|
|
||||||
- refactor: context fragments issues.
|
|
||||||
- logs: Context issues.
|
|
||||||
- fix: Handle empty url issue, again.
|
|
||||||
- fix: Link not opening.
|
|
||||||
- Changelog for v125010201
|
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
**v125010201
|
|
||||||
|
|
||||||
- fix: Handle empty url issue.
|
|
||||||
- Merge pull request 'Removed the floating bar.' (#177) from floating-bar into master
|
|
||||||
- chore: changing actions in reader fragment.
|
|
||||||
- Changelog for v125010131
|
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
**v125010131
|
|
||||||
|
|
||||||
- fix: reload the adapter when it's needed. Fixes #128. (#176)
|
|
||||||
- feat: basic auth and images loading. Fixes #172. (#175)
|
|
||||||
- Changelog for v125010111
|
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
**v125010111
|
|
||||||
|
|
||||||
- Debug trying to fix context issues. (#174)
|
|
||||||
- Changelog for v125010031
|
|
||||||
|
|
||||||
--------------------------------------------------------------------
|
|
||||||
|
|
||||||
**v125010031
|
**v125010031
|
||||||
|
|
||||||
- Merge pull request 'Bump dependencies' (#173) from upgarde into master
|
- Merge pull request 'Bump dependencies' (#173) from upgarde into master
|
||||||
|
@@ -12,38 +12,28 @@ plugins {
|
|||||||
id("app.cash.sqldelight") version "2.0.2"
|
id("app.cash.sqldelight") version "2.0.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Project.execWithOutput(
|
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
|
||||||
cmd: String,
|
val result: String = ByteArrayOutputStream().use { outputStream ->
|
||||||
ignore: Boolean = false,
|
project.exec {
|
||||||
): String {
|
commandLine = cmd.split(" ")
|
||||||
val result: String =
|
standardOutput = outputStream
|
||||||
ByteArrayOutputStream().use { outputStream ->
|
isIgnoreExitValue = ignore
|
||||||
project.exec {
|
|
||||||
commandLine = cmd.split(" ")
|
|
||||||
standardOutput = outputStream
|
|
||||||
isIgnoreExitValue = ignore
|
|
||||||
}
|
|
||||||
outputStream.toString()
|
|
||||||
}
|
}
|
||||||
|
outputStream.toString()
|
||||||
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
fun gitVersion(): String {
|
fun gitVersion(): String {
|
||||||
val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true)
|
val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true)
|
||||||
val process =
|
val process = if (maybeTagOfCurrentCommit.isEmpty()) {
|
||||||
if (maybeTagOfCurrentCommit.isEmpty()) {
|
println("No tag on current commit. Will take the latest one.")
|
||||||
println("No tag on current commit. Will take the latest one.")
|
execWithOutput("git -C ../ for-each-ref refs/tags --sort=-refname --format='%(refname:short)' --count=1")
|
||||||
execWithOutput("git -C ../ for-each-ref refs/tags --sort=-refname --format='%(refname:short)' --count=1")
|
} else {
|
||||||
} else {
|
println("Tag found on current commit")
|
||||||
println("Tag found on current commit")
|
execWithOutput("git -C ../ describe --contains HEAD")
|
||||||
execWithOutput("git -C ../ describe --contains HEAD")
|
}
|
||||||
}
|
return process.replace("^0", "").replace("'", "").substring(1).replace("\\.", "").trim()
|
||||||
return process
|
|
||||||
.replace("^0", "")
|
|
||||||
.replace("'", "")
|
|
||||||
.substring(1)
|
|
||||||
.replace("\\.", "")
|
|
||||||
.trim()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun versionCodeFromGit(): Int {
|
fun versionCodeFromGit(): Int {
|
||||||
@@ -126,6 +116,7 @@ android {
|
|||||||
isIncludeAndroidResources = true
|
isIncludeAndroidResources = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -150,7 +141,7 @@ dependencies {
|
|||||||
implementation("androidx.constraintlayout:constraintlayout:2.2.0")
|
implementation("androidx.constraintlayout:constraintlayout:2.2.0")
|
||||||
implementation("org.jsoup:jsoup:1.18.3")
|
implementation("org.jsoup:jsoup:1.18.3")
|
||||||
|
|
||||||
// multidex
|
//multidex
|
||||||
implementation("androidx.multidex:multidex:2.0.1")
|
implementation("androidx.multidex:multidex:2.0.1")
|
||||||
|
|
||||||
// About
|
// About
|
||||||
@@ -165,34 +156,37 @@ dependencies {
|
|||||||
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
|
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
|
||||||
|
|
||||||
// Themes
|
// Themes
|
||||||
implementation("com.leinardi.android:speed-dial:3.3.0")
|
implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
|
||||||
|
|
||||||
// Pager
|
// Pager
|
||||||
implementation("me.relex:circleindicator:2.1.6")
|
implementation("me.relex:circleindicator:2.1.6")
|
||||||
implementation("androidx.viewpager2:viewpager2:1.1.0")
|
implementation("androidx.viewpager2:viewpager2:1.1.0")
|
||||||
|
|
||||||
// Dependency Injection
|
//Dependency Injection
|
||||||
implementation("org.kodein.di:kodein-di:7.23.1")
|
implementation("org.kodein.di:kodein-di:7.23.1")
|
||||||
implementation("org.kodein.di:kodein-di-framework-android-x:7.23.1")
|
implementation("org.kodein.di:kodein-di-framework-android-x:7.23.1")
|
||||||
implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.23.1")
|
implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.23.1")
|
||||||
|
|
||||||
// Settings
|
//Settings
|
||||||
implementation("com.russhwolf:multiplatform-settings-no-arg:1.3.0")
|
implementation("com.russhwolf:multiplatform-settings-no-arg:1.3.0")
|
||||||
|
|
||||||
// Logging
|
//Logging
|
||||||
implementation("io.github.aakira:napier:2.7.1")
|
implementation("io.github.aakira:napier:2.7.1")
|
||||||
|
|
||||||
// PhotoView
|
//PhotoView
|
||||||
implementation("com.github.chrisbanes:PhotoView:2.3.0")
|
implementation("com.github.chrisbanes:PhotoView:2.3.0")
|
||||||
|
|
||||||
implementation("androidx.core:core-ktx:1.15.0")
|
implementation("androidx.core:core-ktx:1.15.0")
|
||||||
|
|
||||||
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
|
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
|
||||||
|
|
||||||
|
// Network information
|
||||||
|
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
|
||||||
|
|
||||||
// SQLDELIGHT
|
// SQLDELIGHT
|
||||||
implementation("app.cash.sqldelight:android-driver:2.0.2")
|
implementation("app.cash.sqldelight:android-driver:2.0.2")
|
||||||
|
|
||||||
// test
|
//test
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
testImplementation("io.mockk:mockk:1.13.14")
|
testImplementation("io.mockk:mockk:1.13.14")
|
||||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
|
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
|
||||||
@@ -216,12 +210,11 @@ tasks.withType<Test> {
|
|||||||
useJUnit()
|
useJUnit()
|
||||||
testLogging {
|
testLogging {
|
||||||
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
||||||
events =
|
events = setOf(
|
||||||
setOf(
|
org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED,
|
||||||
org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED,
|
org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED,
|
||||||
org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED,
|
org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR
|
||||||
org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR,
|
)
|
||||||
)
|
|
||||||
showStandardStreams = true
|
showStandardStreams = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,4 +227,4 @@ aboutLibraries {
|
|||||||
strictMode = com.mikepenz.aboutlibraries.plugin.StrictMode.FAIL
|
strictMode = com.mikepenz.aboutlibraries.plugin.StrictMode.FAIL
|
||||||
duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE
|
duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE
|
||||||
duplicationRule = com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP
|
duplicationRule = com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP
|
||||||
}
|
}
|
@@ -104,14 +104,12 @@ fun testAddSourceWithUrl(
|
|||||||
onView(withId(R.id.fab))
|
onView(withId(R.id.fab))
|
||||||
.perform(click())
|
.perform(click())
|
||||||
onView(withId(R.id.nameInput))
|
onView(withId(R.id.nameInput))
|
||||||
.perform(click())
|
.perform(click()).perform(typeTextIntoFocusedView(sourceName))
|
||||||
.perform(typeTextIntoFocusedView(sourceName))
|
|
||||||
onView(withId(R.id.sourceUri))
|
onView(withId(R.id.sourceUri))
|
||||||
.perform(click())
|
.perform(click())
|
||||||
.perform(typeTextIntoFocusedView(url))
|
.perform(typeTextIntoFocusedView(url))
|
||||||
onView(withId(R.id.tags))
|
onView(withId(R.id.tags))
|
||||||
.perform(click())
|
.perform(click()).perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
|
||||||
.perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
|
|
||||||
onView(withId(R.id.spoutsSpinner))
|
onView(withId(R.id.spoutsSpinner))
|
||||||
.perform(click())
|
.perform(click())
|
||||||
onData(hasToString("RSS Feed")).perform(click())
|
onData(hasToString("RSS Feed")).perform(click())
|
||||||
|
@@ -30,21 +30,28 @@ fun withError(
|
|||||||
): TypeSafeMatcher<View?> {
|
): TypeSafeMatcher<View?> {
|
||||||
return object : TypeSafeMatcher<View?>() {
|
return object : TypeSafeMatcher<View?>() {
|
||||||
override fun matchesSafely(view: View?): Boolean {
|
override fun matchesSafely(view: View?): Boolean {
|
||||||
if (view != null && (view !is EditText || view.error == null)) {
|
if (view == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val context = view.context
|
||||||
|
if (view !is EditText) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (view.error == null) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
val context = view!!.context
|
|
||||||
|
|
||||||
return (view as EditText).error.toString() == context.getString(id)
|
return view.error.toString() == context.getString(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun describeTo(description: Description?) {
|
override fun describeTo(description: Description?) {
|
||||||
// Nothing
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPopupWindow(): Matcher<Root> = isPlatformPopup()
|
fun isPopupWindow(): Matcher<Root> {
|
||||||
|
return isPlatformPopup()
|
||||||
|
}
|
||||||
|
|
||||||
fun withDrawable(
|
fun withDrawable(
|
||||||
@DrawableRes id: Int,
|
@DrawableRes id: Int,
|
||||||
@@ -53,7 +60,6 @@ fun withDrawable(
|
|||||||
description.appendText("ImageView with drawable same as drawable with id $id")
|
description.appendText("ImageView with drawable same as drawable with id $id")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
override fun matchesSafely(view: View): Boolean {
|
override fun matchesSafely(view: View): Boolean {
|
||||||
val context = view.context
|
val context = view.context
|
||||||
val expectedBitmap = context.getDrawable(id)!!.toBitmap()
|
val expectedBitmap = context.getDrawable(id)!!.toBitmap()
|
||||||
@@ -67,8 +73,8 @@ fun withDrawable(
|
|||||||
|
|
||||||
fun hasBottombarItemText(
|
fun hasBottombarItemText(
|
||||||
@StringRes id: Int,
|
@StringRes id: Int,
|
||||||
): Matcher<View>? =
|
): Matcher<View>? {
|
||||||
allOf(
|
return allOf(
|
||||||
withResourceName("fixed_bottom_navigation_icon"),
|
withResourceName("fixed_bottom_navigation_icon"),
|
||||||
withParent(
|
withParent(
|
||||||
allOf(
|
allOf(
|
||||||
@@ -77,21 +83,23 @@ fun hasBottombarItemText(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun withSettingsCheckboxWidget(
|
fun withSettingsCheckboxWidget(
|
||||||
@StringRes id: Int,
|
@StringRes id: Int,
|
||||||
): Matcher<View>? =
|
): Matcher<View>? {
|
||||||
allOf(
|
return allOf(
|
||||||
withId(android.R.id.switch_widget),
|
withId(android.R.id.switch_widget),
|
||||||
withParent(
|
withParent(
|
||||||
withSettingsCheckboxFrame(id),
|
withSettingsCheckboxFrame(id),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun withSettingsCheckboxFrame(
|
fun withSettingsCheckboxFrame(
|
||||||
@StringRes id: Int,
|
@StringRes id: Int,
|
||||||
): Matcher<View>? =
|
): Matcher<View>? {
|
||||||
allOf(
|
return allOf(
|
||||||
withId(android.R.id.widget_frame),
|
withId(android.R.id.widget_frame),
|
||||||
hasSibling(
|
hasSibling(
|
||||||
allOf(
|
allOf(
|
||||||
@@ -102,6 +110,7 @@ fun withSettingsCheckboxFrame(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun openMenu() {
|
fun openMenu() {
|
||||||
openActionBarOverflowOrOptionsMenu(
|
openActionBarOverflowOrOptionsMenu(
|
||||||
|
@@ -56,7 +56,7 @@ class HomeActivityTest {
|
|||||||
fun testMenuActions() {
|
fun testMenuActions() {
|
||||||
onView(withId(R.id.action_search)).perform(click())
|
onView(withId(R.id.action_search)).perform(click())
|
||||||
onView(
|
onView(
|
||||||
withId(com.google.android.material.R.id.search_src_text),
|
withId(R.id.search_src_text),
|
||||||
).check(matches(isFocused()))
|
).check(matches(isFocused()))
|
||||||
onView(isRoot()).perform(ViewActions.pressBack())
|
onView(isRoot()).perform(ViewActions.pressBack())
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android
|
package bou.amine.apps.readerforselfossv2.android
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import androidx.test.espresso.Espresso.onView
|
import androidx.test.espresso.Espresso.onView
|
||||||
import androidx.test.espresso.IdlingRegistry
|
import androidx.test.espresso.IdlingRegistry
|
||||||
import androidx.test.espresso.action.ViewActions.click
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
@@ -25,33 +26,35 @@ class LoginActivityTest {
|
|||||||
@get:Rule
|
@get:Rule
|
||||||
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
|
private fun getActivity(): Activity? {
|
||||||
|
var activity: Activity? = null
|
||||||
|
activityRule.scenario.onActivity {
|
||||||
|
activity = it
|
||||||
|
}
|
||||||
|
return activity
|
||||||
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun registerIdlingResource() {
|
fun registerIdlingResource() {
|
||||||
IdlingRegistry
|
IdlingRegistry.getInstance()
|
||||||
.getInstance()
|
|
||||||
.register(CountingIdlingResourceSingleton.countingIdlingResource)
|
.register(CountingIdlingResourceSingleton.countingIdlingResource)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun unregisterIdlingResource() {
|
fun unregisterIdlingResource() {
|
||||||
IdlingRegistry
|
IdlingRegistry.getInstance()
|
||||||
.getInstance()
|
|
||||||
.unregister(CountingIdlingResourceSingleton.countingIdlingResource)
|
.unregister(CountingIdlingResourceSingleton.countingIdlingResource)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun viewIsInitialized() {
|
fun viewIsInitialized() {
|
||||||
onView(withId(R.id.urlView)).check(matches(isDisplayed()))
|
onView(withId(R.id.urlView)).check(matches(isDisplayed()))
|
||||||
onView(withId(R.id.selfSigned))
|
onView(withId(R.id.selfSigned)).check(matches(isDisplayed())).check(matches(isNotChecked()))
|
||||||
.check(matches(isDisplayed()))
|
|
||||||
.check(matches(isNotChecked()))
|
|
||||||
.check(
|
.check(
|
||||||
matches(isClickable()),
|
matches(isClickable()),
|
||||||
)
|
)
|
||||||
onView(withId(R.id.withLogin))
|
onView(withId(R.id.withLogin)).check(matches(isDisplayed()))
|
||||||
.check(matches(isDisplayed()))
|
.check(matches(isNotChecked())).check(
|
||||||
.check(matches(isNotChecked()))
|
|
||||||
.check(
|
|
||||||
matches(isClickable()),
|
matches(isClickable()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -60,23 +63,9 @@ class LoginActivityTest {
|
|||||||
fun urlError() {
|
fun urlError() {
|
||||||
performLogin("10.0.2.2:8888")
|
performLogin("10.0.2.2:8888")
|
||||||
onView(withId(R.id.urlView)).perform(click())
|
onView(withId(R.id.urlView)).perform(click())
|
||||||
onView(withId(R.id.urlView)).check(matches(withError(R.string.login_url_problem)))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun connectError() {
|
|
||||||
performLogin("http://10.0.2.2:8889")
|
|
||||||
onView(withId(R.id.urlView)).perform(click())
|
|
||||||
onView(withId(R.id.urlView)).check(matches(withError(R.string.wrong_infos)))
|
onView(withId(R.id.urlView)).check(matches(withError(R.string.wrong_infos)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun urlSlashError() {
|
|
||||||
performLogin("https://google.fr/toto")
|
|
||||||
onView(withId(R.id.urlView)).perform(click())
|
|
||||||
onView(withId(R.id.urlView)).check(matches(withError(R.string.login_url_problem)))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun multiError() {
|
fun multiError() {
|
||||||
onView(withId(R.id.signInButton)).perform(click())
|
onView(withId(R.id.signInButton)).perform(click())
|
||||||
|
@@ -42,7 +42,6 @@ class SettingsActivityGeneralTest {
|
|||||||
onView(withText(R.string.pref_header_general)).perform(click())
|
onView(withText(R.string.pref_header_general)).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:LongMethod")
|
|
||||||
@Test
|
@Test
|
||||||
fun testGeneral() {
|
fun testGeneral() {
|
||||||
onView(withText(R.string.pref_api_items_number_title)).check(matches(isDisplayed()))
|
onView(withText(R.string.pref_api_items_number_title)).check(matches(isDisplayed()))
|
||||||
@@ -65,6 +64,19 @@ class SettingsActivityGeneralTest {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.reader_static_bar_title)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
not(isChecked()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||||
|
matches(
|
||||||
|
isEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
onView(withText(R.string.pref_general_category_displaying)).check(matches(isDisplayed()))
|
onView(withText(R.string.pref_general_category_displaying)).check(matches(isDisplayed()))
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).check(
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).check(
|
||||||
matches(
|
matches(
|
||||||
@@ -106,7 +118,6 @@ class SettingsActivityGeneralTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:ForbiddenComment")
|
|
||||||
@Test
|
@Test
|
||||||
fun testGeneralActionsNumberItems() {
|
fun testGeneralActionsNumberItems() {
|
||||||
onView(withText(R.string.pref_api_items_number_title)).perform(click())
|
onView(withText(R.string.pref_api_items_number_title)).perform(click())
|
||||||
@@ -148,6 +159,19 @@ class SettingsActivityGeneralTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGeneralActionsCheckboxes() {
|
fun testGeneralActionsCheckboxes() {
|
||||||
|
// article viewer settings
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||||
|
matches(
|
||||||
|
isEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.pref_article_viewer_title)).perform(click())
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||||
|
matches(
|
||||||
|
not(isEnabled()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(not(isEnabled())))
|
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(not(isEnabled())))
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).perform(click())
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).perform(click())
|
||||||
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(isEnabled()))
|
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(isEnabled()))
|
||||||
|
@@ -42,7 +42,6 @@ class SettingsActivityOfflineTest {
|
|||||||
onView(withText(R.string.pref_header_offline)).perform(click())
|
onView(withText(R.string.pref_header_offline)).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:LongMethod")
|
|
||||||
@Test
|
@Test
|
||||||
fun testOffline() {
|
fun testOffline() {
|
||||||
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).check(
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).check(
|
||||||
@@ -108,7 +107,6 @@ class SettingsActivityOfflineTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:LongMethod")
|
|
||||||
@Test
|
@Test
|
||||||
fun testOfflineActions() {
|
fun testOfflineActions() {
|
||||||
onView(withText(R.string.pref_switch_items_caching_off)).check(matches(isDisplayed()))
|
onView(withText(R.string.pref_switch_items_caching_off)).check(matches(isDisplayed()))
|
||||||
|
@@ -45,7 +45,6 @@ class SourcesActivityTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
@Test
|
@Test
|
||||||
fun addSourceCheckContent() {
|
fun addSourceCheckContent() {
|
||||||
testAddSourceWithUrl("https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en", sourceName)
|
testAddSourceWithUrl("https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en", sourceName)
|
||||||
|
@@ -31,11 +31,11 @@ import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
|
|||||||
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
|
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
|
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.bottombar.removeBadge
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowserAsNewTask
|
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
import bou.amine.apps.readerforselfossv2.utils.Enums.ItemType
|
||||||
import com.ashokvarma.bottomnavigation.BottomNavigationBar
|
import com.ashokvarma.bottomnavigation.BottomNavigationBar
|
||||||
import com.ashokvarma.bottomnavigation.BottomNavigationItem
|
import com.ashokvarma.bottomnavigation.BottomNavigationItem
|
||||||
import com.ashokvarma.bottomnavigation.TextBadgeItem
|
import com.ashokvarma.bottomnavigation.TextBadgeItem
|
||||||
@@ -49,8 +49,6 @@ import org.kodein.di.instance
|
|||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
private const val MIN_WIDTH_CARD_DP = 300
|
|
||||||
|
|
||||||
class HomeActivity :
|
class HomeActivity :
|
||||||
AppCompatActivity(),
|
AppCompatActivity(),
|
||||||
SearchView.OnQueryTextListener,
|
SearchView.OnQueryTextListener,
|
||||||
@@ -202,7 +200,6 @@ class HomeActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:LongMethod")
|
|
||||||
private fun handleBottomBar() {
|
private fun handleBottomBar() {
|
||||||
tabNewBadge =
|
tabNewBadge =
|
||||||
TextBadgeItem()
|
TextBadgeItem()
|
||||||
@@ -285,7 +282,7 @@ class HomeActivity :
|
|||||||
|
|
||||||
handleBottomBarActions()
|
handleBottomBarActions()
|
||||||
|
|
||||||
handleGdprDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
|
handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
|
||||||
|
|
||||||
handleRecurringTask()
|
handleRecurringTask()
|
||||||
CountingIdlingResourceSingleton.increment()
|
CountingIdlingResourceSingleton.increment()
|
||||||
@@ -297,10 +294,10 @@ class HomeActivity :
|
|||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleGdprDialog(gdprShown: Boolean) {
|
private fun handleGDPRDialog(GDPRShown: Boolean) {
|
||||||
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
|
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
|
||||||
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
|
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
|
||||||
if (!gdprShown) {
|
if (!GDPRShown) {
|
||||||
val alertDialog = AlertDialog.Builder(this).create()
|
val alertDialog = AlertDialog.Builder(this).create()
|
||||||
alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
|
alertDialog.setTitle(getString(R.string.gdpr_dialog_title))
|
||||||
alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
|
alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
|
||||||
@@ -317,44 +314,50 @@ class HomeActivity :
|
|||||||
|
|
||||||
private fun reloadLayoutManager() {
|
private fun reloadLayoutManager() {
|
||||||
val currentManager = binding.recyclerView.layoutManager
|
val currentManager = binding.recyclerView.layoutManager
|
||||||
|
val layoutManager: RecyclerView.LayoutManager
|
||||||
|
|
||||||
fun gridLayoutManager() {
|
// This will only update the layout manager if settings changed
|
||||||
val layoutManager =
|
|
||||||
GridLayoutManager(
|
|
||||||
this,
|
|
||||||
calculateNoOfColumns(),
|
|
||||||
)
|
|
||||||
binding.recyclerView.layoutManager = layoutManager
|
|
||||||
}
|
|
||||||
|
|
||||||
fun staggererdGridLayoutManager() {
|
|
||||||
var layoutManager =
|
|
||||||
StaggeredGridLayoutManager(
|
|
||||||
calculateNoOfColumns(),
|
|
||||||
StaggeredGridLayoutManager.VERTICAL,
|
|
||||||
)
|
|
||||||
layoutManager.gapStrategy =
|
|
||||||
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
|
|
||||||
binding.recyclerView.layoutManager = layoutManager
|
|
||||||
}
|
|
||||||
|
|
||||||
when (currentManager) {
|
when (currentManager) {
|
||||||
is StaggeredGridLayoutManager ->
|
is StaggeredGridLayoutManager ->
|
||||||
if (!appSettingsService.isCardViewEnabled()) {
|
if (!appSettingsService.isCardViewEnabled()) {
|
||||||
gridLayoutManager()
|
layoutManager =
|
||||||
|
GridLayoutManager(
|
||||||
|
this,
|
||||||
|
calculateNoOfColumns(),
|
||||||
|
)
|
||||||
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
}
|
}
|
||||||
|
|
||||||
is GridLayoutManager ->
|
is GridLayoutManager ->
|
||||||
if (appSettingsService.isCardViewEnabled()) {
|
if (appSettingsService.isCardViewEnabled()) {
|
||||||
staggererdGridLayoutManager()
|
layoutManager =
|
||||||
|
StaggeredGridLayoutManager(
|
||||||
|
calculateNoOfColumns(),
|
||||||
|
StaggeredGridLayoutManager.VERTICAL,
|
||||||
|
)
|
||||||
|
layoutManager.gapStrategy =
|
||||||
|
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
|
||||||
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
}
|
}
|
||||||
|
|
||||||
else ->
|
else ->
|
||||||
if (currentManager == null) {
|
if (currentManager == null) {
|
||||||
if (!appSettingsService.isCardViewEnabled()) {
|
if (!appSettingsService.isCardViewEnabled()) {
|
||||||
gridLayoutManager()
|
layoutManager =
|
||||||
|
GridLayoutManager(
|
||||||
|
this,
|
||||||
|
calculateNoOfColumns(),
|
||||||
|
)
|
||||||
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
} else {
|
} else {
|
||||||
staggererdGridLayoutManager()
|
layoutManager =
|
||||||
|
StaggeredGridLayoutManager(
|
||||||
|
calculateNoOfColumns(),
|
||||||
|
StaggeredGridLayoutManager.VERTICAL,
|
||||||
|
)
|
||||||
|
layoutManager.gapStrategy =
|
||||||
|
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
|
||||||
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -479,8 +482,8 @@ class HomeActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun handleListResult(appendResults: Boolean = false) {
|
private fun handleListResult(appendResults: Boolean = false) {
|
||||||
val oldManager = binding.recyclerView.layoutManager
|
|
||||||
if (appendResults) {
|
if (appendResults) {
|
||||||
|
val oldManager = binding.recyclerView.layoutManager
|
||||||
firstVisible =
|
firstVisible =
|
||||||
when (oldManager) {
|
when (oldManager) {
|
||||||
is StaggeredGridLayoutManager ->
|
is StaggeredGridLayoutManager ->
|
||||||
@@ -493,13 +496,7 @@ class HomeActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:ComplexCondition")
|
if (recyclerAdapter == null) {
|
||||||
if (recyclerAdapter == null ||
|
|
||||||
(
|
|
||||||
(recyclerAdapter is ItemListAdapter && appSettingsService.isCardViewEnabled()) ||
|
|
||||||
(recyclerAdapter is ItemCardAdapter && !appSettingsService.isCardViewEnabled())
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (appSettingsService.isCardViewEnabled()) {
|
if (appSettingsService.isCardViewEnabled()) {
|
||||||
recyclerAdapter =
|
recyclerAdapter =
|
||||||
ItemCardAdapter(
|
ItemCardAdapter(
|
||||||
@@ -546,7 +543,7 @@ class HomeActivity :
|
|||||||
private fun calculateNoOfColumns(): Int {
|
private fun calculateNoOfColumns(): Int {
|
||||||
val displayMetrics = resources.displayMetrics
|
val displayMetrics = resources.displayMetrics
|
||||||
val dpWidth = displayMetrics.widthPixels / displayMetrics.density
|
val dpWidth = displayMetrics.widthPixels / displayMetrics.density
|
||||||
return (dpWidth / MIN_WIDTH_CARD_DP).toInt()
|
return (dpWidth / 300).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueryTextChange(p0: String?): Boolean {
|
override fun onQueryTextChange(p0: String?): Boolean {
|
||||||
@@ -595,11 +592,10 @@ class HomeActivity :
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:ReturnCount", "detekt:LongMethod")
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.issue_tracker -> {
|
R.id.issue_tracker -> {
|
||||||
baseContext.openUrlInBrowserAsNewTask(AppSettingsService.BUG_URL)
|
baseContext.openUrlInBrowser(AppSettingsService.TRACKER_URL)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -84,9 +84,7 @@ class ImageActivity : AppCompatActivity() {
|
|||||||
return super.onOptionsItemSelected(item)
|
return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class ScreenSlidePagerAdapter(
|
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
|
||||||
fa: FragmentActivity,
|
|
||||||
) : FragmentStateAdapter(fa) {
|
|
||||||
override fun getItemCount(): Int = allImages.size
|
override fun getItemCount(): Int = allImages.size
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position])
|
override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position])
|
||||||
|
@@ -30,8 +30,6 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
private const val MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED = 3
|
|
||||||
|
|
||||||
class LoginActivity :
|
class LoginActivity :
|
||||||
AppCompatActivity(),
|
AppCompatActivity(),
|
||||||
DIAware {
|
DIAware {
|
||||||
@@ -136,23 +134,13 @@ class LoginActivity :
|
|||||||
binding.passwordView.error = null
|
binding.passwordView.error = null
|
||||||
|
|
||||||
// Store values at the time of the login attempt.
|
// Store values at the time of the login attempt.
|
||||||
val url =
|
val url = binding.urlView.text.toString().trim()
|
||||||
binding.urlView.text
|
val login = binding.loginView.text.toString().trim()
|
||||||
.toString()
|
val password = binding.passwordView.text.toString().trim()
|
||||||
.trim()
|
|
||||||
val login =
|
failInvalidUrl(url)
|
||||||
binding.loginView.text
|
failLoginDetails(password, login)
|
||||||
.toString()
|
|
||||||
.trim()
|
|
||||||
val password =
|
|
||||||
binding.passwordView.text
|
|
||||||
.toString()
|
|
||||||
.trim()
|
|
||||||
|
|
||||||
val cancelUrl = failInvalidUrl(url)
|
|
||||||
if (cancelUrl) return
|
|
||||||
val cancelDetails = failLoginDetails(password, login)
|
|
||||||
if (cancelDetails) return
|
|
||||||
showProgress(true)
|
showProgress(true)
|
||||||
|
|
||||||
appSettingsService.updateSelfSigned(binding.selfSigned.isChecked)
|
appSettingsService.updateSelfSigned(binding.selfSigned.isChecked)
|
||||||
@@ -194,7 +182,7 @@ class LoginActivity :
|
|||||||
private fun failLoginDetails(
|
private fun failLoginDetails(
|
||||||
password: String,
|
password: String,
|
||||||
login: String,
|
login: String,
|
||||||
): Boolean {
|
) {
|
||||||
var lastFocusedView: View? = null
|
var lastFocusedView: View? = null
|
||||||
var cancel = false
|
var cancel = false
|
||||||
if (isWithLogin) {
|
if (isWithLogin) {
|
||||||
@@ -211,17 +199,16 @@ class LoginActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
maybeCancelAndFocusView(cancel, lastFocusedView)
|
maybeCancelAndFocusView(cancel, lastFocusedView)
|
||||||
return cancel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun failInvalidUrl(url: String): Boolean {
|
private fun failInvalidUrl(url: String) {
|
||||||
val focusView = binding.urlView
|
val focusView = binding.urlView
|
||||||
var cancel = false
|
var cancel = false
|
||||||
if (url.isBaseUrlInvalid()) {
|
if (url.isBaseUrlInvalid()) {
|
||||||
cancel = true
|
cancel = true
|
||||||
binding.urlView.error = getString(R.string.login_url_problem)
|
binding.urlView.error = getString(R.string.login_url_problem)
|
||||||
inValidCount++
|
inValidCount++
|
||||||
if (inValidCount == MAX_INVALID_LOGIN_BEFORE_ALERT_DISPLAYED) {
|
if (inValidCount == 3) {
|
||||||
val alertDialog = AlertDialog.Builder(this).create()
|
val alertDialog = AlertDialog.Builder(this).create()
|
||||||
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
alertDialog.setTitle(getString(R.string.warning_wrong_url))
|
||||||
alertDialog.setMessage(getString(R.string.text_wrong_url))
|
alertDialog.setMessage(getString(R.string.text_wrong_url))
|
||||||
@@ -234,7 +221,6 @@ class LoginActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
maybeCancelAndFocusView(cancel, focusView)
|
maybeCancelAndFocusView(cancel, focusView)
|
||||||
return cancel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeCancelAndFocusView(
|
private fun maybeCancelAndFocusView(
|
||||||
@@ -287,7 +273,7 @@ class LoginActivity :
|
|||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
R.id.issue_tracker -> {
|
R.id.issue_tracker -> {
|
||||||
val browserIntent =
|
val browserIntent =
|
||||||
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.BUG_URL))
|
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.TRACKER_URL))
|
||||||
startActivity(browserIntent)
|
startActivity(browserIntent)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -297,7 +283,7 @@ class LoginActivity :
|
|||||||
.withAboutIconShown(true)
|
.withAboutIconShown(true)
|
||||||
.withAboutVersionShown(true)
|
.withAboutVersionShown(true)
|
||||||
.withAboutSpecial2("Bug reports")
|
.withAboutSpecial2("Bug reports")
|
||||||
.withAboutSpecial2Description(AppSettingsService.BUG_URL)
|
.withAboutSpecial2Description(AppSettingsService.TRACKER_URL)
|
||||||
.withAboutSpecial1("Project Page")
|
.withAboutSpecial1("Project Page")
|
||||||
.withAboutSpecial1Description(AppSettingsService.SOURCE_URL)
|
.withAboutSpecial1Description(AppSettingsService.SOURCE_URL)
|
||||||
.start(this)
|
.start(this)
|
||||||
|
@@ -10,16 +10,18 @@ import androidx.lifecycle.LifecycleOwner
|
|||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.multidex.MultiDexApplication
|
import androidx.multidex.MultiDexApplication
|
||||||
import bou.amine.apps.readerforselfossv2.android.testing.TestingHelper
|
import bou.amine.apps.readerforselfossv2.android.testing.TestingHelper
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
|
||||||
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
|
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
|
||||||
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
||||||
import bou.amine.apps.readerforselfossv2.di.networkModule
|
import bou.amine.apps.readerforselfossv2.di.networkModule
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import bou.amine.apps.readerforselfossv2.service.ConnectivityService
|
import com.github.ln_12.library.ConnectivityStatus
|
||||||
import io.github.aakira.napier.DebugAntilog
|
import io.github.aakira.napier.DebugAntilog
|
||||||
import io.github.aakira.napier.Napier
|
import io.github.aakira.napier.Napier
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.acra.ACRA
|
import org.acra.ACRA
|
||||||
import org.acra.ReportField
|
import org.acra.ReportField
|
||||||
@@ -42,21 +44,26 @@ class MyApp :
|
|||||||
import(networkModule)
|
import(networkModule)
|
||||||
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
|
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
|
||||||
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
|
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
|
||||||
bind<ConnectivityService>() with singleton { ConnectivityService() }
|
|
||||||
bind<Repository>() with
|
bind<Repository>() with
|
||||||
singleton {
|
singleton {
|
||||||
Repository(
|
Repository(
|
||||||
instance(),
|
instance(),
|
||||||
instance(),
|
instance(),
|
||||||
instance(),
|
isConnectionAvailable,
|
||||||
instance(),
|
instance(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
|
||||||
|
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val repository: Repository by 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 val driverFactory: DriverFactory by instance()
|
||||||
private val connectivityService: ConnectivityService by instance()
|
|
||||||
|
// TODO: handle with the "previous" way
|
||||||
|
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
@@ -69,12 +76,13 @@ class MyApp :
|
|||||||
|
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(
|
ProcessLifecycleOwner.get().lifecycle.addObserver(
|
||||||
AppLifeCycleObserver(
|
AppLifeCycleObserver(
|
||||||
connectivityService,
|
connectivityStatus,
|
||||||
|
repository,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
connectivityService.networkAvailableProvider.collect { networkAvailable ->
|
viewModel.networkAvailableProvider.collect { networkAvailable ->
|
||||||
val toastMessage =
|
val toastMessage =
|
||||||
if (networkAvailable) {
|
if (networkAvailable) {
|
||||||
repository.handleDBActions()
|
repository.handleDBActions()
|
||||||
@@ -100,7 +108,6 @@ class MyApp :
|
|||||||
super.attachBaseContext(base)
|
super.attachBaseContext(base)
|
||||||
|
|
||||||
initAcra {
|
initAcra {
|
||||||
sendReportsInDevMode = false
|
|
||||||
reportFormat = StringFormat.JSON
|
reportFormat = StringFormat.JSON
|
||||||
reportContent =
|
reportContent =
|
||||||
listOf(
|
listOf(
|
||||||
@@ -153,7 +160,7 @@ class MyApp :
|
|||||||
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
|
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
|
||||||
val newItemsChannelmChannel =
|
val newItemsChannelmChannel =
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
AppSettingsService.NEW_ITEMS_CHANNEL,
|
AppSettingsService.NEW_ITEMS_CHANNEL_ID,
|
||||||
newItemsChannelname,
|
newItemsChannelname,
|
||||||
newItemsChannelimportance,
|
newItemsChannelimportance,
|
||||||
)
|
)
|
||||||
@@ -180,15 +187,18 @@ class MyApp :
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AppLifeCycleObserver(
|
class AppLifeCycleObserver(
|
||||||
val connectivityService: ConnectivityService,
|
val connectivityStatus: ConnectivityStatus,
|
||||||
|
val repository: Repository,
|
||||||
) : DefaultLifecycleObserver {
|
) : DefaultLifecycleObserver {
|
||||||
override fun onResume(owner: LifecycleOwner) {
|
override fun onResume(owner: LifecycleOwner) {
|
||||||
super.onResume(owner)
|
super.onResume(owner)
|
||||||
connectivityService.start()
|
repository.connectionMonitored = true
|
||||||
|
connectivityStatus.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause(owner: LifecycleOwner) {
|
override fun onPause(owner: LifecycleOwner) {
|
||||||
connectivityService.stop()
|
repository.connectionMonitored = false
|
||||||
|
connectivityStatus.stop()
|
||||||
super.onPause(owner)
|
super.onPause(owner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,9 +22,7 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
class ReaderActivity :
|
class ReaderActivity : AppCompatActivity(), DIAware {
|
||||||
AppCompatActivity(),
|
|
||||||
DIAware {
|
|
||||||
private var currentItem: Int = 0
|
private var currentItem: Int = 0
|
||||||
|
|
||||||
private lateinit var toolbarMenu: Menu
|
private lateinit var toolbarMenu: Menu
|
||||||
@@ -53,7 +51,6 @@ class ReaderActivity :
|
|||||||
showMenuItem(false)
|
showMenuItem(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityReaderBinding.inflate(layoutInflater)
|
binding = ActivityReaderBinding.inflate(layoutInflater)
|
||||||
@@ -102,9 +99,8 @@ class ReaderActivity :
|
|||||||
oldInstanceState.clear()
|
oldInstanceState.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class ScreenSlidePagerAdapter(
|
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) :
|
||||||
fa: FragmentActivity,
|
FragmentStateAdapter(fa) {
|
||||||
) : FragmentStateAdapter(fa) {
|
|
||||||
override fun getItemCount(): Int = allItems.size
|
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])
|
||||||
@@ -113,8 +109,8 @@ class ReaderActivity :
|
|||||||
override fun onKeyDown(
|
override fun onKeyDown(
|
||||||
keyCode: Int,
|
keyCode: Int,
|
||||||
event: KeyEvent?,
|
event: KeyEvent?,
|
||||||
): Boolean =
|
): Boolean {
|
||||||
when (keyCode) {
|
return when (keyCode) {
|
||||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||||
val currentFragment =
|
val currentFragment =
|
||||||
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
||||||
@@ -133,6 +129,7 @@ class ReaderActivity :
|
|||||||
super.onKeyDown(keyCode, event)
|
super.onKeyDown(keyCode, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun alignmentMenu() {
|
private fun alignmentMenu() {
|
||||||
val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT
|
val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT
|
||||||
@@ -161,14 +158,12 @@ class ReaderActivity :
|
|||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
super.onPageSelected(position)
|
super.onPageSelected(position)
|
||||||
|
|
||||||
if (!allItems.isNullOrEmpty() && allItems.size >= position) {
|
if (allItems[position].starred) {
|
||||||
if (allItems[position].starred) {
|
canRemoveFromFavorite()
|
||||||
canRemoveFromFavorite()
|
} else {
|
||||||
} else {
|
canFavorite()
|
||||||
canFavorite()
|
|
||||||
}
|
|
||||||
readItem(allItems[position])
|
|
||||||
}
|
}
|
||||||
|
readItem(allItems[position])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@@ -18,9 +18,7 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
class SourcesActivity :
|
class SourcesActivity : AppCompatActivity(), DIAware {
|
||||||
AppCompatActivity(),
|
|
||||||
DIAware {
|
|
||||||
private lateinit var binding: ActivitySourcesBinding
|
private lateinit var binding: ActivitySourcesBinding
|
||||||
|
|
||||||
override val di by closestDI()
|
override val di by closestDI()
|
||||||
@@ -70,12 +68,11 @@ class SourcesActivity :
|
|||||||
binding.recyclerView.adapter = mAdapter
|
binding.recyclerView.adapter = mAdapter
|
||||||
mAdapter.notifyDataSetChanged()
|
mAdapter.notifyDataSetChanged()
|
||||||
} else {
|
} else {
|
||||||
Toast
|
Toast.makeText(
|
||||||
.makeText(
|
this@SourcesActivity,
|
||||||
this@SourcesActivity,
|
R.string.cant_get_sources,
|
||||||
R.string.cant_get_sources,
|
Toast.LENGTH_SHORT,
|
||||||
Toast.LENGTH_SHORT,
|
).show()
|
||||||
).show()
|
|
||||||
}
|
}
|
||||||
CountingIdlingResourceSingleton.decrement()
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
|
@@ -9,9 +9,11 @@ import android.widget.TextView
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityUpsertSourceBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityUpsertSourceBinding
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
|
||||||
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
|
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -19,9 +21,7 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.closestDI
|
import org.kodein.di.android.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
class UpsertSourceActivity :
|
class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
||||||
AppCompatActivity(),
|
|
||||||
DIAware {
|
|
||||||
private var existingSource: SelfossModel.SourceDetail? = null
|
private var existingSource: SelfossModel.SourceDetail? = null
|
||||||
private var mSpoutsValue: String? = null
|
private var mSpoutsValue: String? = null
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ class UpsertSourceActivity :
|
|||||||
|
|
||||||
override val di by closestDI()
|
override val di by closestDI()
|
||||||
private val repository: Repository by instance()
|
private val repository: Repository by instance()
|
||||||
|
private val appSettingsService: AppSettingsService by instance()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -73,10 +74,15 @@ class UpsertSourceActivity :
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
handleSpoutsSpinner()
|
|
||||||
|
val baseUrl = appSettingsService.getBaseUrl()
|
||||||
|
if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid()) {
|
||||||
|
mustLoginToAddSource()
|
||||||
|
} else {
|
||||||
|
handleSpoutsSpinner()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
private fun handleSpoutsSpinner() {
|
private fun handleSpoutsSpinner() {
|
||||||
val spoutsKV = HashMap<String, String>()
|
val spoutsKV = HashMap<String, String>()
|
||||||
binding.spoutsSpinner.onItemSelectedListener =
|
binding.spoutsSpinner.onItemSelectedListener =
|
||||||
@@ -99,12 +105,11 @@ class UpsertSourceActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun handleSpoutFailure(networkIssue: Boolean = false) {
|
fun handleSpoutFailure(networkIssue: Boolean = false) {
|
||||||
Toast
|
Toast.makeText(
|
||||||
.makeText(
|
this@UpsertSourceActivity,
|
||||||
this@UpsertSourceActivity,
|
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
|
||||||
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
|
Toast.LENGTH_SHORT,
|
||||||
Toast.LENGTH_SHORT,
|
).show()
|
||||||
).show()
|
|
||||||
binding.progress.visibility = View.GONE
|
binding.progress.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +153,13 @@ class UpsertSourceActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun mustLoginToAddSource() {
|
||||||
|
Toast.makeText(this, getString(R.string.addStringNoUrl), Toast.LENGTH_SHORT).show()
|
||||||
|
val i = Intent(this, LoginActivity::class.java)
|
||||||
|
startActivity(i)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleSaveSource() {
|
private fun handleSaveSource() {
|
||||||
val url = binding.sourceUri.text.toString()
|
val url = binding.sourceUri.text.toString()
|
||||||
|
|
||||||
@@ -158,7 +170,6 @@ class UpsertSourceActivity :
|
|||||||
sourceDetailsUnavailable -> {
|
sourceDetailsUnavailable -> {
|
||||||
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val successfullyAddedSource =
|
val successfullyAddedSource =
|
||||||
@@ -181,12 +192,11 @@ class UpsertSourceActivity :
|
|||||||
if (successfullyAddedSource) {
|
if (successfullyAddedSource) {
|
||||||
finish()
|
finish()
|
||||||
} else {
|
} else {
|
||||||
Toast
|
Toast.makeText(
|
||||||
.makeText(
|
this@UpsertSourceActivity,
|
||||||
this@UpsertSourceActivity,
|
R.string.cant_create_source,
|
||||||
R.string.cant_create_source,
|
Toast.LENGTH_SHORT,
|
||||||
Toast.LENGTH_SHORT,
|
).show()
|
||||||
).show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -118,18 +118,16 @@ class ItemCardAdapter(
|
|||||||
binding.itemImage.setImageDrawable(null)
|
binding.itemImage.setImageDrawable(null)
|
||||||
} else {
|
} else {
|
||||||
binding.itemImage.visibility = View.VISIBLE
|
binding.itemImage.visibility = View.VISIBLE
|
||||||
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage, appSettingsService)
|
c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||||
binding.sourceImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
binding.sourceImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
||||||
} else {
|
} else {
|
||||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage, appSettingsService)
|
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(
|
inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root)
|
||||||
val binding: CardItemBinding,
|
|
||||||
) : RecyclerView.ViewHolder(binding.root)
|
|
||||||
}
|
}
|
||||||
|
@@ -65,15 +65,13 @@ class ItemListAdapter(
|
|||||||
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||||
binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
|
||||||
} else {
|
} else {
|
||||||
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage, appSettingsService)
|
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage, appSettingsService)
|
c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ViewHolder(
|
inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root)
|
||||||
val binding: ListItemBinding,
|
|
||||||
) : RecyclerView.ViewHolder(binding.root)
|
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@ import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
|
|||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
import bou.amine.apps.readerforselfossv2.utils.Enums.ItemType
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@@ -6,8 +6,9 @@ import android.content.Intent
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.Button
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity
|
import bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity
|
||||||
@@ -15,7 +16,6 @@ import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBindi
|
|||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
|
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -29,23 +29,68 @@ import org.kodein.di.instance
|
|||||||
class SourcesListAdapter(
|
class SourcesListAdapter(
|
||||||
private val app: Activity,
|
private val app: Activity,
|
||||||
private val items: ArrayList<SelfossModel.SourceDetail>,
|
private val items: ArrayList<SelfossModel.SourceDetail>,
|
||||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(),
|
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
|
||||||
DIAware {
|
private val c: Context = app.baseContext
|
||||||
|
private lateinit var binding: SourceListItemBinding
|
||||||
|
|
||||||
override val di: DI by closestDI(app)
|
override val di: DI by closestDI(app)
|
||||||
|
private val repository: Repository by instance()
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int,
|
viewType: Int,
|
||||||
): ViewHolder {
|
): ViewHolder {
|
||||||
val binding = SourceListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
binding = SourceListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return ViewHolder(binding)
|
return ViewHolder(binding.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(
|
override fun onBindViewHolder(
|
||||||
holder: ViewHolder,
|
holder: ViewHolder,
|
||||||
position: Int,
|
position: Int,
|
||||||
) {
|
) {
|
||||||
holder.bind(items[position], position)
|
val itm = items[position]
|
||||||
|
|
||||||
|
val deleteBtn: Button = holder.mView.findViewById(R.id.deleteBtn)
|
||||||
|
|
||||||
|
deleteBtn.setOnClickListener {
|
||||||
|
val (id, title) = items[position]
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val successfullyDeletedSource = repository.deleteSource(id, title)
|
||||||
|
if (successfullyDeletedSource) {
|
||||||
|
items.removeAt(position)
|
||||||
|
notifyItemRemoved(position)
|
||||||
|
notifyItemRangeChanged(position, itemCount)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
app,
|
||||||
|
R.string.can_delete_source,
|
||||||
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.mView.setOnClickListener {
|
||||||
|
val source = items[position]
|
||||||
|
|
||||||
|
repository.setSelectedSource(source)
|
||||||
|
app.startActivity(Intent(app, UpsertSourceActivity::class.java))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itm.getIcon(repository.baseUrl).isEmpty()) {
|
||||||
|
binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded())
|
||||||
|
} else {
|
||||||
|
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!itm.error.isNullOrBlank()) {
|
||||||
|
binding.errorText.visibility = View.VISIBLE
|
||||||
|
binding.errorText.text = itm.error
|
||||||
|
} else {
|
||||||
|
binding.errorText.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.sourceTitle.text = itm.title.getHtmlDecoded()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemId(position: Int) = position.toLong()
|
override fun getItemId(position: Int) = position.toLong()
|
||||||
@@ -54,73 +99,5 @@ class SourcesListAdapter(
|
|||||||
|
|
||||||
override fun getItemCount(): Int = items.size
|
override fun getItemCount(): Int = items.size
|
||||||
|
|
||||||
inner class ViewHolder(
|
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView)
|
||||||
val binding: SourceListItemBinding,
|
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
|
||||||
private val context: Context = app.applicationContext
|
|
||||||
private val repository: Repository by instance()
|
|
||||||
private val appSettingsService: AppSettingsService by instance()
|
|
||||||
|
|
||||||
fun bind(
|
|
||||||
source: SelfossModel.SourceDetail,
|
|
||||||
position: Int,
|
|
||||||
) {
|
|
||||||
binding.apply {
|
|
||||||
sourceTitle.text = source.title.getHtmlDecoded()
|
|
||||||
if (source.getIcon(repository.baseUrl).isEmpty()) {
|
|
||||||
itemImage.setBackgroundAndText(source.title.getHtmlDecoded())
|
|
||||||
} else {
|
|
||||||
context.circularDrawable(source.getIcon(repository.baseUrl), itemImage, appSettingsService)
|
|
||||||
}
|
|
||||||
|
|
||||||
errorText.apply {
|
|
||||||
visibility = if (!source.error.isNullOrBlank()) View.VISIBLE else View.GONE
|
|
||||||
text = source.error
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteBtn.setOnClickListener { showDeleteConfirmationDialog(source, position) }
|
|
||||||
|
|
||||||
root.setOnClickListener {
|
|
||||||
repository.setSelectedSource(source)
|
|
||||||
app.startActivity(Intent(app, UpsertSourceActivity::class.java))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showDeleteConfirmationDialog(
|
|
||||||
source: SelfossModel.SourceDetail,
|
|
||||||
position: Int,
|
|
||||||
) {
|
|
||||||
AlertDialog
|
|
||||||
.Builder(app)
|
|
||||||
.setTitle(app.getString(R.string.confirm_delete_title))
|
|
||||||
.setMessage(app.getString(R.string.confirm_delete_message, source.title))
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ -> deleteSource(source, position) }
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteSource(
|
|
||||||
source: SelfossModel.SourceDetail,
|
|
||||||
position: Int,
|
|
||||||
) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val successfullyDeletedSource = repository.deleteSource(source.id, source.title)
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
if (successfullyDeletedSource) {
|
|
||||||
items.removeAt(position)
|
|
||||||
notifyItemRemoved(position)
|
|
||||||
notifyItemRangeChanged(position, itemCount)
|
|
||||||
} else {
|
|
||||||
Toast
|
|
||||||
.makeText(
|
|
||||||
app,
|
|
||||||
R.string.can_delete_source,
|
|
||||||
Toast.LENGTH_SHORT,
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -26,8 +26,6 @@ import org.kodein.di.instance
|
|||||||
import java.util.Timer
|
import java.util.Timer
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
private const val NOTIFICATION_DELAY = 4000L
|
|
||||||
|
|
||||||
class LoadingWorker(
|
class LoadingWorker(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
params: WorkerParameters,
|
params: WorkerParameters,
|
||||||
@@ -63,7 +61,7 @@ class LoadingWorker(
|
|||||||
handleNewItemsNotification(apiItems, notificationManager)
|
handleNewItemsNotification(apiItems, notificationManager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
apiItems.map { it.preloadImages(context, appSettingsService) }
|
apiItems.map { it.preloadImages(context) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Result.success()
|
return Result.success()
|
||||||
@@ -95,7 +93,7 @@ class LoadingWorker(
|
|||||||
NotificationCompat
|
NotificationCompat
|
||||||
.Builder(
|
.Builder(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
AppSettingsService.NEW_ITEMS_CHANNEL,
|
AppSettingsService.NEW_ITEMS_CHANNEL_ID,
|
||||||
).setContentTitle(context.getString(R.string.new_items_notification_title))
|
).setContentTitle(context.getString(R.string.new_items_notification_title))
|
||||||
.setContentText(
|
.setContentText(
|
||||||
context.getString(
|
context.getString(
|
||||||
@@ -103,16 +101,16 @@ class LoadingWorker(
|
|||||||
newSize,
|
newSize,
|
||||||
),
|
),
|
||||||
).setPriority(PRIORITY_DEFAULT)
|
).setPriority(PRIORITY_DEFAULT)
|
||||||
.setChannelId(AppSettingsService.NEW_ITEMS_CHANNEL)
|
.setChannelId(AppSettingsService.NEW_ITEMS_CHANNEL_ID)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
||||||
|
|
||||||
Timer("", false).schedule(NOTIFICATION_DELAY) {
|
Timer("", false).schedule(4000) {
|
||||||
notificationManager.notify(2, newItemsNotification.build())
|
notificationManager.notify(2, newItemsNotification.build())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timer("", false).schedule(NOTIFICATION_DELAY) {
|
Timer("", false).schedule(4000) {
|
||||||
notificationManager.cancel(1)
|
notificationManager.cancel(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,14 +2,18 @@ package bou.amine.apps.readerforselfossv2.android.fragments
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.TypedArray
|
import android.content.res.TypedArray
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.TypedValue
|
||||||
import android.util.TypedValue.DATA_NULL_UNDEFINED
|
import android.util.TypedValue.DATA_NULL_UNDEFINED
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.InflateException
|
import android.view.InflateException
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -19,6 +23,7 @@ import android.webkit.WebView
|
|||||||
import android.webkit.WebViewClient
|
import android.webkit.WebViewClient
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.core.widget.NestedScrollView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import bou.amine.apps.readerforselfossv2.android.ImageActivity
|
import bou.amine.apps.readerforselfossv2.android.ImageActivity
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
@@ -27,27 +32,25 @@ import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem
|
|||||||
import bou.amine.apps.readerforselfossv2.android.model.toModel
|
import bou.amine.apps.readerforselfossv2.android.model.toModel
|
||||||
import bou.amine.apps.readerforselfossv2.android.model.toParcelable
|
import bou.amine.apps.readerforselfossv2.android.model.toParcelable
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.addHomeMadeActionItem
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.getColorFromAttr
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapFitCenter
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
|
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.getGlideImageForResource
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.isUrlValid
|
import bou.amine.apps.readerforselfossv2.android.utils.isUrlValid
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.maybeIfContext
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrlInBrowserAsNewTask
|
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrlInBrowserAsNewTask
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowserAsNewTask
|
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
||||||
import bou.amine.apps.readerforselfossv2.model.MercuryModel
|
import bou.amine.apps.readerforselfossv2.model.MercuryModel
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
|
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import bou.amine.apps.readerforselfossv2.service.ConnectivityService
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getImages
|
import bou.amine.apps.readerforselfossv2.utils.getImages
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
|
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
|
||||||
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
||||||
import com.leinardi.android.speeddial.SpeedDialView
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -62,38 +65,28 @@ import java.util.Locale
|
|||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
|
|
||||||
private const val IMAGE_JPG = "image/jpg"
|
private const val IMAGE_JPG = "image/jpg"
|
||||||
private const val IMAGE_PNG = "image/png"
|
|
||||||
private const val IMAGE_WEBP = "image/webp"
|
|
||||||
|
|
||||||
private const val WHITE_COLOR_HEX = 0xFFFFFF
|
class ArticleFragment : Fragment(), DIAware {
|
||||||
|
private var fontSize: Int = 16
|
||||||
private const val DEFAULT_FONT_SIZE = 16
|
|
||||||
|
|
||||||
class ArticleFragment :
|
|
||||||
Fragment(),
|
|
||||||
DIAware {
|
|
||||||
private var colorOnSurface: Int = 0
|
|
||||||
private var colorSurface: Int = 0
|
|
||||||
private var fontSize: Int = DEFAULT_FONT_SIZE
|
|
||||||
private lateinit var item: SelfossModel.Item
|
private lateinit var item: SelfossModel.Item
|
||||||
private var url: String? = null
|
private lateinit var url: String
|
||||||
private lateinit var contentText: String
|
private lateinit var contentText: String
|
||||||
private lateinit var contentSource: String
|
private lateinit var contentSource: String
|
||||||
private lateinit var contentImage: String
|
private lateinit var contentImage: String
|
||||||
private lateinit var contentTitle: String
|
private lateinit var contentTitle: String
|
||||||
private lateinit var allImages: ArrayList<String>
|
private lateinit var allImages: ArrayList<String>
|
||||||
private lateinit var fab: SpeedDialView
|
private lateinit var fab: FloatingActionButton
|
||||||
private lateinit var textAlignment: String
|
private lateinit var textAlignment: String
|
||||||
private lateinit var binding: FragmentArticleBinding
|
private lateinit var binding: FragmentArticleBinding
|
||||||
|
|
||||||
override val di: DI by closestDI()
|
override val di: DI by closestDI()
|
||||||
private val repository: Repository by instance()
|
private val repository: Repository by instance()
|
||||||
private val appSettingsService: AppSettingsService by instance()
|
private val appSettingsService: AppSettingsService by instance()
|
||||||
private val connectivityService: ConnectivityService by instance()
|
|
||||||
|
|
||||||
private var typeface: Typeface? = null
|
private var typeface: Typeface? = null
|
||||||
private var resId: Int = 0
|
private var resId: Int = 0
|
||||||
private var font = ""
|
private var font = ""
|
||||||
|
private var staticBar = false
|
||||||
|
|
||||||
private val mercuryApi: MercuryApi by instance()
|
private val mercuryApi: MercuryApi by instance()
|
||||||
|
|
||||||
@@ -105,7 +98,6 @@ class ArticleFragment :
|
|||||||
item = pi.toModel()
|
item = pi.toModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:LongMethod")
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
@@ -120,9 +112,6 @@ class ArticleFragment :
|
|||||||
e.sendSilentlyWithAcra()
|
e.sendSilentlyWithAcra()
|
||||||
}
|
}
|
||||||
|
|
||||||
colorOnSurface = getColorFromAttr(com.google.android.material.R.attr.colorOnSurface)
|
|
||||||
colorSurface = getColorFromAttr(com.google.android.material.R.attr.colorSurface)
|
|
||||||
|
|
||||||
contentText = item.content
|
contentText = item.content
|
||||||
contentTitle = item.title.getHtmlDecoded()
|
contentTitle = item.title.getHtmlDecoded()
|
||||||
contentImage = item.getThumbnail(repository.baseUrl)
|
contentImage = item.getThumbnail(repository.baseUrl)
|
||||||
@@ -136,11 +125,23 @@ class ArticleFragment :
|
|||||||
allImages = item.getImages()
|
allImages = item.getImages()
|
||||||
|
|
||||||
fontSize = appSettingsService.getFontSize()
|
fontSize = appSettingsService.getFontSize()
|
||||||
|
staticBar = appSettingsService.isStaticBarEnabled()
|
||||||
font = appSettingsService.getFont()
|
font = appSettingsService.getFont()
|
||||||
|
|
||||||
refreshAlignment()
|
refreshAlignment()
|
||||||
|
|
||||||
handleFloatingToolbar()
|
fab = binding.fab
|
||||||
|
|
||||||
|
fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent))
|
||||||
|
|
||||||
|
fab.rippleColor = resources.getColor(R.color.colorAccentDark)
|
||||||
|
|
||||||
|
val floatingToolbar: FloatingToolbar = handleFloatingToolbar()
|
||||||
|
|
||||||
|
if (staticBar) {
|
||||||
|
fab.hide()
|
||||||
|
floatingToolbar.show()
|
||||||
|
}
|
||||||
|
|
||||||
binding.source.text = contentSource
|
binding.source.text = contentSource
|
||||||
if (typeface != null) {
|
if (typeface != null) {
|
||||||
@@ -148,20 +149,37 @@ class ArticleFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleContent()
|
handleContent()
|
||||||
|
|
||||||
|
binding.nestedScrollView.setOnScrollChangeListener(
|
||||||
|
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||||
|
if (scrollY > oldScrollY) {
|
||||||
|
floatingToolbar.hide()
|
||||||
|
fab.hide()
|
||||||
|
} else {
|
||||||
|
if (staticBar) {
|
||||||
|
floatingToolbar.show()
|
||||||
|
} else {
|
||||||
|
if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
} catch (e: InflateException) {
|
} catch (e: InflateException) {
|
||||||
e.sendSilentlyWithAcraWithName("webview not available")
|
e.sendSilentlyWithAcraWithName("webview not available")
|
||||||
maybeIfContext {
|
try {
|
||||||
AlertDialog
|
AlertDialog.Builder(requireContext())
|
||||||
.Builder(it)
|
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
||||||
.setMessage(it.getString(R.string.webview_dialog_issue_message))
|
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
||||||
.setTitle(it.getString(R.string.webview_dialog_issue_title))
|
|
||||||
.setPositiveButton(
|
.setPositiveButton(
|
||||||
android.R.string.ok,
|
android.R.string.ok,
|
||||||
) { _, _ ->
|
) { _, _ ->
|
||||||
appSettingsService.disableArticleViewer()
|
appSettingsService.disableArticleViewer()
|
||||||
requireActivity().finish()
|
requireActivity().finish()
|
||||||
}.create()
|
}
|
||||||
|
.create()
|
||||||
.show()
|
.show()
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,8 +188,8 @@ class ArticleFragment :
|
|||||||
|
|
||||||
private fun handleContent() {
|
private fun handleContent() {
|
||||||
if (contentText.isEmptyOrNullOrNullString()) {
|
if (contentText.isEmptyOrNullOrNullString()) {
|
||||||
if (connectivityService.isNetworkAvailable() && url.isUrlValid()) {
|
if (repository.isNetworkAvailable()) {
|
||||||
getContentFromMercury(url!!)
|
getContentFromMercury()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.titleView.text = contentTitle
|
binding.titleView.text = contentTitle
|
||||||
@@ -183,84 +201,70 @@ class ArticleFragment :
|
|||||||
|
|
||||||
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
|
if (!contentImage.isEmptyOrNullOrNullString() && context != null) {
|
||||||
binding.imageView.visibility = View.VISIBLE
|
binding.imageView.visibility = View.VISIBLE
|
||||||
maybeIfContext { it.bitmapFitCenter(contentImage, binding.imageView, appSettingsService) }
|
Glide
|
||||||
|
.with(requireContext())
|
||||||
|
.asBitmap()
|
||||||
|
.load(contentImage)
|
||||||
|
.apply(RequestOptions.fitCenterTransform())
|
||||||
|
.into(binding.imageView)
|
||||||
} else {
|
} else {
|
||||||
binding.imageView.visibility = View.GONE
|
binding.imageView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleFloatingToolbar() {
|
private fun handleFloatingToolbar(): FloatingToolbar {
|
||||||
fab = binding.speedDial
|
val floatingToolbar: FloatingToolbar = binding.floatingToolbar
|
||||||
fab.mainFabClosedIconColor = colorOnSurface
|
if (appSettingsService.getPublicAccess()) {
|
||||||
fab.mainFabOpenedIconColor = colorOnSurface
|
floatingToolbar.setMenu(R.menu.reader_toolbar_no_read)
|
||||||
|
|
||||||
maybeIfContext { handleFloatingToolbarActionItems(it) }
|
|
||||||
|
|
||||||
fab.setOnActionSelectedListener { actionItem ->
|
|
||||||
when (actionItem.id) {
|
|
||||||
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
|
|
||||||
R.id.open_action -> requireActivity().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
|
|
||||||
R.id.unread_action ->
|
|
||||||
if (this@ArticleFragment.item.unread) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
repository.markAsRead(this@ArticleFragment.item)
|
|
||||||
}
|
|
||||||
this@ArticleFragment.item.unread = false
|
|
||||||
maybeIfContext {
|
|
||||||
Toast
|
|
||||||
.makeText(
|
|
||||||
it,
|
|
||||||
R.string.marked_as_read,
|
|
||||||
Toast.LENGTH_LONG,
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
repository.unmarkAsRead(this@ArticleFragment.item)
|
|
||||||
}
|
|
||||||
this@ArticleFragment.item.unread = true
|
|
||||||
maybeIfContext {
|
|
||||||
Toast
|
|
||||||
.makeText(
|
|
||||||
it,
|
|
||||||
R.string.marked_as_unread,
|
|
||||||
Toast.LENGTH_LONG,
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
floatingToolbar.attachFab(fab)
|
||||||
|
|
||||||
private fun handleFloatingToolbarActionItems(c: Context) {
|
floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
|
||||||
fab.addHomeMadeActionItem(
|
|
||||||
R.id.share_action,
|
floatingToolbar.setClickListener(
|
||||||
resources.getDrawable(R.drawable.ic_share_white_24dp),
|
object : FloatingToolbar.ItemClickListener {
|
||||||
R.string.reader_action_share,
|
override fun onItemClick(item: MenuItem) {
|
||||||
colorOnSurface,
|
when (item.itemId) {
|
||||||
colorSurface,
|
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
|
||||||
c,
|
R.id.open_action -> requireActivity().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
|
||||||
)
|
R.id.unread_action ->
|
||||||
fab.addHomeMadeActionItem(
|
try {
|
||||||
R.id.open_action,
|
if (this@ArticleFragment.item.unread) {
|
||||||
resources.getDrawable(R.drawable.ic_open_in_browser_white_24dp),
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
R.string.reader_action_open,
|
repository.markAsRead(this@ArticleFragment.item)
|
||||||
colorOnSurface,
|
}
|
||||||
colorSurface,
|
this@ArticleFragment.item.unread = false
|
||||||
c,
|
Toast.makeText(
|
||||||
)
|
requireContext(),
|
||||||
fab.addHomeMadeActionItem(
|
R.string.marked_as_read,
|
||||||
R.id.unread_action,
|
Toast.LENGTH_LONG,
|
||||||
resources.getDrawable(R.drawable.ic_baseline_white_eye_24dp),
|
).show()
|
||||||
R.string.unmark,
|
} else {
|
||||||
colorOnSurface,
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
colorSurface,
|
repository.unmarkAsRead(this@ArticleFragment.item)
|
||||||
c,
|
}
|
||||||
|
this@ArticleFragment.item.unread = true
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
R.string.marked_as_unread,
|
||||||
|
Toast.LENGTH_LONG,
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemLongClick(item: MenuItem?) {
|
||||||
|
// We do nothing
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
return floatingToolbar
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshAlignment() {
|
private fun refreshAlignment() {
|
||||||
@@ -272,8 +276,7 @@ class ArticleFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
private fun getContentFromMercury() {
|
||||||
private fun getContentFromMercury(url: String) {
|
|
||||||
binding.progressBar.visibility = View.VISIBLE
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@@ -311,12 +314,17 @@ class ArticleFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLeadImage(leadImageUrl: String?) {
|
private fun handleLeadImage(lead_image_url: String?) {
|
||||||
if (!leadImageUrl.isNullOrEmpty()) {
|
if (!lead_image_url.isNullOrEmpty() && context != null) {
|
||||||
maybeIfContext {
|
binding.imageView.visibility = View.VISIBLE
|
||||||
binding.imageView.visibility = View.VISIBLE
|
Glide
|
||||||
it.bitmapFitCenter(leadImageUrl, binding.imageView, appSettingsService)
|
.with(requireContext())
|
||||||
}
|
.asBitmap()
|
||||||
|
.load(
|
||||||
|
lead_image_url,
|
||||||
|
)
|
||||||
|
.apply(RequestOptions.fitCenterTransform())
|
||||||
|
.into(binding.imageView)
|
||||||
} else {
|
} else {
|
||||||
binding.imageView.visibility = View.GONE
|
binding.imageView.visibility = View.GONE
|
||||||
}
|
}
|
||||||
@@ -329,85 +337,125 @@ class ArticleFragment :
|
|||||||
override fun shouldOverrideUrlLoading(
|
override fun shouldOverrideUrlLoading(
|
||||||
view: WebView?,
|
view: WebView?,
|
||||||
url: String,
|
url: String,
|
||||||
): Boolean =
|
): Boolean {
|
||||||
if (url.isUrlValid() &&
|
return if (context != null &&
|
||||||
|
url.isUrlValid() &&
|
||||||
binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
||||||
) {
|
) {
|
||||||
maybeIfContext { it.openUrlInBrowserAsNewTask(url) }
|
requireContext().openUrlInBrowser(url)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException", "detekt:ReturnCount")
|
|
||||||
@Deprecated("Deprecated in Java")
|
@Deprecated("Deprecated in Java")
|
||||||
override fun shouldInterceptRequest(
|
override fun shouldInterceptRequest(
|
||||||
view: WebView,
|
view: WebView,
|
||||||
url: String,
|
url: String,
|
||||||
): WebResourceResponse? {
|
): WebResourceResponse? {
|
||||||
val (mime: String?, compression: Bitmap.CompressFormat) =
|
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||||
if (url
|
if (url.lowercase(Locale.US).contains(".jpg") ||
|
||||||
.lowercase(Locale.US)
|
url.lowercase(Locale.US)
|
||||||
.contains(".jpg") ||
|
.contains(".jpeg")
|
||||||
url.lowercase(Locale.US).contains(".jpeg")
|
) {
|
||||||
) {
|
try {
|
||||||
Pair(IMAGE_JPG, Bitmap.CompressFormat.JPEG)
|
val image =
|
||||||
} else if (url.lowercase(Locale.US).contains(".png")) {
|
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
||||||
Pair(IMAGE_PNG, Bitmap.CompressFormat.PNG)
|
.get()
|
||||||
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
return WebResourceResponse(
|
||||||
Pair(IMAGE_WEBP, Bitmap.CompressFormat.WEBP)
|
IMAGE_JPG,
|
||||||
} else {
|
"UTF-8",
|
||||||
return super.shouldInterceptRequest(view, url)
|
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG),
|
||||||
|
)
|
||||||
|
} catch (e: ExecutionException) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
} else if (url.lowercase(Locale.US).contains(".png")) {
|
||||||
|
try {
|
||||||
|
val image =
|
||||||
|
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
||||||
|
.get()
|
||||||
|
return WebResourceResponse(
|
||||||
|
IMAGE_JPG,
|
||||||
|
"UTF-8",
|
||||||
|
getBitmapInputStream(image, Bitmap.CompressFormat.PNG),
|
||||||
|
)
|
||||||
|
} catch (e: ExecutionException) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
||||||
|
try {
|
||||||
|
val image =
|
||||||
|
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
||||||
|
.get()
|
||||||
|
return WebResourceResponse(
|
||||||
|
IMAGE_JPG,
|
||||||
|
"UTF-8",
|
||||||
|
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP),
|
||||||
|
)
|
||||||
|
} catch (e: ExecutionException) {
|
||||||
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
val image = view.getGlideImageForResource(url, appSettingsService)
|
|
||||||
return WebResourceResponse(
|
|
||||||
mime,
|
|
||||||
"UTF-8",
|
|
||||||
getBitmapInputStream(image, compression),
|
|
||||||
)
|
|
||||||
} catch (e: ExecutionException) {
|
|
||||||
return super.shouldInterceptRequest(view, url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return super.shouldInterceptRequest(view, url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:LongMethod", "detekt:ImplicitDefaultLocale")
|
|
||||||
private fun htmlToWebview() {
|
private fun htmlToWebview() {
|
||||||
maybeIfContext {
|
val context: Context
|
||||||
|
try {
|
||||||
|
context = requireContext()
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val colorOnSurface = TypedValue()
|
||||||
|
val colorSurface = TypedValue()
|
||||||
|
|
||||||
|
try {
|
||||||
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
|
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
|
||||||
val a: TypedArray = it.obtainStyledAttributes(resId, attrs)
|
val a: TypedArray = context.obtainStyledAttributes(resId, attrs)
|
||||||
|
|
||||||
binding.webcontent.settings.standardFontFamily = a.getString(0)
|
binding.webcontent.settings.standardFontFamily = a.getString(0)
|
||||||
""
|
binding.webcontent.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
context.theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
|
||||||
|
|
||||||
|
context.theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context issue when setting attributes, but context wasn't null before")
|
||||||
}
|
}
|
||||||
binding.webcontent.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
val colorSurfaceString =
|
val colorSurfaceString =
|
||||||
String.format(
|
String.format(
|
||||||
"#%06X",
|
"#%06X",
|
||||||
WHITE_COLOR_HEX and (if (colorSurface != DATA_NULL_UNDEFINED) colorSurface else WHITE_COLOR_HEX),
|
0xFFFFFF and (if (colorSurface.data != DATA_NULL_UNDEFINED) colorSurface.data else 0xFFFFFF),
|
||||||
)
|
)
|
||||||
|
|
||||||
val colorOnSurfaceString =
|
val colorOnSurfaceString =
|
||||||
String.format(
|
String.format(
|
||||||
"#%06X",
|
"#%06X",
|
||||||
WHITE_COLOR_HEX and (if (colorOnSurface != DATA_NULL_UNDEFINED) colorOnSurface else 0),
|
0xFFFFFF and (if (colorOnSurface.data != DATA_NULL_UNDEFINED) colorOnSurface.data else 0),
|
||||||
)
|
)
|
||||||
|
|
||||||
binding.webcontent.settings.useWideViewPort = true
|
|
||||||
binding.webcontent.settings.loadWithOverviewMode = true
|
|
||||||
binding.webcontent.settings.javaScriptEnabled = false
|
|
||||||
|
|
||||||
handleImageLoading()
|
|
||||||
try {
|
try {
|
||||||
|
binding.webcontent.settings.useWideViewPort = true
|
||||||
|
binding.webcontent.settings.loadWithOverviewMode = true
|
||||||
|
binding.webcontent.settings.javaScriptEnabled = false
|
||||||
|
|
||||||
|
handleImageLoading()
|
||||||
|
|
||||||
val gestureDetector =
|
val gestureDetector =
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
activity,
|
activity,
|
||||||
object : GestureDetector.SimpleOnGestureListener() {
|
object : GestureDetector.SimpleOnGestureListener() {
|
||||||
override fun onSingleTapUp(e: MotionEvent): Boolean = performClick()
|
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||||
|
return performClick()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -416,50 +464,49 @@ class ArticleFragment :
|
|||||||
event,
|
event,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
binding.webcontent.settings.layoutAlgorithm =
|
||||||
|
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
||||||
} catch (e: IllegalStateException) {
|
} catch (e: IllegalStateException) {
|
||||||
e.sendSilentlyWithAcraWithName("Gesture detector issue ?")
|
e.sendSilentlyWithAcraWithName("Context is null but wasn't, and that's causing issues with webview config")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.webcontent.settings.layoutAlgorithm =
|
|
||||||
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
|
|
||||||
|
|
||||||
var baseUrl: String? = null
|
|
||||||
try {
|
try {
|
||||||
val itemUrl = URL(url.orEmpty())
|
var baseUrl: String? = null
|
||||||
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
try {
|
||||||
} catch (e: MalformedURLException) {
|
val itemUrl = URL(url)
|
||||||
e.sendSilentlyWithAcraWithName("htmlToWebview > ${url.orEmpty()}")
|
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
||||||
}
|
} catch (e: MalformedURLException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
|
||||||
|
}
|
||||||
|
|
||||||
val fontName: String =
|
val fontName =
|
||||||
maybeIfContext {
|
|
||||||
when (font) {
|
when (font) {
|
||||||
it.getString(R.string.open_sans_font_id) -> "Open Sans"
|
getString(R.string.open_sans_font_id) -> "Open Sans"
|
||||||
it.getString(R.string.roboto_font_id) -> "Roboto"
|
getString(R.string.roboto_font_id) -> "Roboto"
|
||||||
it.getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
|
getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
}?.toString().orEmpty()
|
|
||||||
|
|
||||||
val fontLinkAndStyle =
|
val fontLinkAndStyle =
|
||||||
if (fontName.isNotEmpty()) {
|
if (font.isNotEmpty()) {
|
||||||
"""<link href="https://fonts.googleapis.com/css?family=${
|
"""<link href="https://fonts.googleapis.com/css?family=${
|
||||||
fontName.replace(
|
fontName.replace(
|
||||||
" ",
|
" ",
|
||||||
"+",
|
"+",
|
||||||
)
|
)
|
||||||
}" rel="stylesheet">
|
}" rel="stylesheet">
|
||||||
|<style>
|
|<style>
|
||||||
| * {
|
| * {
|
||||||
| font-family: '$fontName';
|
| font-family: '$fontName';
|
||||||
| }
|
| }
|
||||||
|</style>
|
|</style>
|
||||||
""".trimMargin()
|
""".trimMargin()
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
binding.webcontent.loadDataWithBaseURL(
|
binding.webcontent.loadDataWithBaseURL(
|
||||||
baseUrl,
|
baseUrl,
|
||||||
"""<html>
|
"""<html>
|
||||||
@@ -476,7 +523,7 @@ class ArticleFragment :
|
|||||||
| color: ${
|
| color: ${
|
||||||
String.format(
|
String.format(
|
||||||
"#%06X",
|
"#%06X",
|
||||||
WHITE_COLOR_HEX and (maybeIfContext { it.resources.getColor(R.color.colorAccent) } as Int),
|
0xFFFFFF and context.resources.getColor(R.color.colorAccent),
|
||||||
)
|
)
|
||||||
} !important;
|
} !important;
|
||||||
| }
|
| }
|
||||||
@@ -533,8 +580,10 @@ class ArticleFragment :
|
|||||||
|
|
||||||
private fun openInBrowserAfterFailing() {
|
private fun openInBrowserAfterFailing() {
|
||||||
binding.progressBar.visibility = View.GONE
|
binding.progressBar.visibility = View.GONE
|
||||||
maybeIfContext {
|
try {
|
||||||
it.openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
|
requireContext().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,8 +600,7 @@ class ArticleFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun performClick(): Boolean {
|
fun performClick(): Boolean {
|
||||||
if (allImages != null &&
|
if (allImages != null && (
|
||||||
(
|
|
||||||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
|
binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
|
||||||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
||||||
)
|
)
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.fragments
|
package bou.amine.apps.readerforselfossv2.android.fragments
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.GradientDrawable
|
import android.graphics.drawable.GradientDrawable
|
||||||
@@ -15,13 +16,11 @@ import bou.amine.apps.readerforselfossv2.android.HomeActivity
|
|||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.imageIntoViewTarget
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.maybeIfContext
|
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getColorHexCode
|
import bou.amine.apps.readerforselfossv2.utils.getColorHexCode
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.target.ViewTarget
|
import com.bumptech.glide.request.target.ViewTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
@@ -34,15 +33,10 @@ import org.kodein.di.DIAware
|
|||||||
import org.kodein.di.android.x.closestDI
|
import org.kodein.di.android.x.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
|
|
||||||
private const val DRAWABLE_SIZE = 30
|
class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
||||||
|
|
||||||
class FilterSheetFragment :
|
|
||||||
BottomSheetDialogFragment(),
|
|
||||||
DIAware {
|
|
||||||
private lateinit var binding: FilterFragmentBinding
|
private lateinit var binding: FilterFragmentBinding
|
||||||
override val di: DI by closestDI()
|
override val di: DI by closestDI()
|
||||||
private val repository: Repository by instance()
|
private val repository: Repository by instance()
|
||||||
private val appSettingsService: AppSettingsService by instance()
|
|
||||||
|
|
||||||
private var selectedChip: Chip? = null
|
private var selectedChip: Chip? = null
|
||||||
|
|
||||||
@@ -60,8 +54,8 @@ class FilterSheetFragment :
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
handleTagChips()
|
handleTagChips(requireContext())
|
||||||
handleSourceChips()
|
handleSourceChips(requireContext())
|
||||||
|
|
||||||
binding.progressBar2.visibility = GONE
|
binding.progressBar2.visibility = GONE
|
||||||
binding.filterView.visibility = VISIBLE
|
binding.filterView.visibility = VISIBLE
|
||||||
@@ -79,24 +73,16 @@ class FilterSheetFragment :
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleSourceChips() {
|
private suspend fun handleSourceChips(context: Context) {
|
||||||
val sourceGroup = binding.sourcesGroup
|
val sourceGroup = binding.sourcesGroup
|
||||||
|
|
||||||
repository.getSourcesDetailsOrStats().forEachIndexed { _, source ->
|
repository.getSourcesDetailsOrStats().forEachIndexed { _, source ->
|
||||||
val c: Chip? =
|
val c = Chip(context)
|
||||||
maybeIfContext {
|
|
||||||
Chip(it)
|
|
||||||
} as Chip?
|
|
||||||
|
|
||||||
if (c == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.ellipsize = TextUtils.TruncateAt.END
|
c.ellipsize = TextUtils.TruncateAt.END
|
||||||
|
|
||||||
maybeIfContext {
|
Glide.with(context)
|
||||||
it.imageIntoViewTarget(
|
.load(source.getIcon(repository.baseUrl))
|
||||||
source.getIcon(repository.baseUrl),
|
.into(
|
||||||
object : ViewTarget<Chip?, Drawable?>(c) {
|
object : ViewTarget<Chip?, Drawable?>(c) {
|
||||||
override fun onResourceReady(
|
override fun onResourceReady(
|
||||||
resource: Drawable,
|
resource: Drawable,
|
||||||
@@ -109,9 +95,7 @@ class FilterSheetFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
appSettingsService,
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
c.text = source.title.getHtmlDecoded()
|
c.text = source.title.getHtmlDecoded()
|
||||||
|
|
||||||
@@ -147,17 +131,13 @@ class FilterSheetFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleTagChips() {
|
private suspend fun handleTagChips(context: Context) {
|
||||||
val tagGroup = binding.tagsGroup
|
val tagGroup = binding.tagsGroup
|
||||||
|
|
||||||
val tags = repository.getTags()
|
val tags = repository.getTags()
|
||||||
|
|
||||||
tags.forEachIndexed { _, tag ->
|
tags.forEachIndexed { _, tag ->
|
||||||
val c: Chip? = maybeIfContext { Chip(it) } as Chip?
|
val c = Chip(context)
|
||||||
if (c == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.ellipsize = TextUtils.TruncateAt.END
|
c.ellipsize = TextUtils.TruncateAt.END
|
||||||
c.text = tag.tag
|
c.text = tag.tag
|
||||||
|
|
||||||
@@ -173,8 +153,8 @@ class FilterSheetFragment :
|
|||||||
}
|
}
|
||||||
gd.setColor(gdColor)
|
gd.setColor(gdColor)
|
||||||
gd.shape = GradientDrawable.RECTANGLE
|
gd.shape = GradientDrawable.RECTANGLE
|
||||||
gd.setSize(DRAWABLE_SIZE, DRAWABLE_SIZE)
|
gd.setSize(30, 30)
|
||||||
gd.cornerRadius = DRAWABLE_SIZE.toFloat()
|
gd.cornerRadius = 30F
|
||||||
c.chipIcon = gd
|
c.chipIcon = gd
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
|
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
|
||||||
|
@@ -6,21 +6,15 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentImageBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentImageBinding
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapWithCache
|
import com.bumptech.glide.Glide
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import org.kodein.di.DI
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import org.kodein.di.DIAware
|
|
||||||
import org.kodein.di.android.x.closestDI
|
|
||||||
import org.kodein.di.instance
|
|
||||||
|
|
||||||
class ImageFragment :
|
class ImageFragment : Fragment() {
|
||||||
Fragment(),
|
|
||||||
DIAware {
|
|
||||||
override val di: DI by closestDI()
|
|
||||||
private val appSettingsService: AppSettingsService by instance()
|
|
||||||
private lateinit var imageUrl: String
|
private lateinit var imageUrl: String
|
||||||
|
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||||
private var _binding: FragmentImageBinding? = null
|
private var _binding: FragmentImageBinding? = null
|
||||||
val binding get() = _binding
|
private val binding get() = _binding
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -37,7 +31,11 @@ class ImageFragment :
|
|||||||
val view = binding?.root
|
val view = binding?.root
|
||||||
|
|
||||||
binding!!.photoView.visibility = View.VISIBLE
|
binding!!.photoView.visibility = View.VISIBLE
|
||||||
requireActivity().bitmapWithCache(imageUrl, binding!!.photoView, appSettingsService)
|
Glide.with(requireActivity())
|
||||||
|
.asBitmap()
|
||||||
|
.apply(glideOptions)
|
||||||
|
.load(imageUrl)
|
||||||
|
.into(binding!!.photoView)
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
@@ -3,21 +3,23 @@ package bou.amine.apps.readerforselfossv2.android.model
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.webkit.URLUtil
|
import android.webkit.URLUtil
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.preloadImage
|
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getImages
|
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
|
||||||
|
|
||||||
fun SelfossModel.Item.preloadImages(
|
fun SelfossModel.Item.preloadImages(context: Context): Boolean {
|
||||||
context: Context,
|
|
||||||
appSettingsService: AppSettingsService,
|
|
||||||
): Boolean {
|
|
||||||
val imageUrls = this.getImages()
|
val imageUrls = this.getImages()
|
||||||
|
|
||||||
|
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (url in imageUrls) {
|
for (url in imageUrls) {
|
||||||
if (URLUtil.isValidUrl(url)) {
|
if (URLUtil.isValidUrl(url)) {
|
||||||
context.preloadImage(url, appSettingsService)
|
Glide.with(context).asBitmap()
|
||||||
|
.apply(glideOptions)
|
||||||
|
.load(url).submit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
|
@@ -26,10 +26,6 @@ import org.kodein.di.android.closestDI
|
|||||||
|
|
||||||
private const val TITLE_TAG = "settingsActivityTitle"
|
private const val TITLE_TAG = "settingsActivityTitle"
|
||||||
|
|
||||||
const val MAX_ITEMS_NUMBER = 200
|
|
||||||
|
|
||||||
private const val MIN_ITEMS_NUMBER = 1
|
|
||||||
|
|
||||||
class SettingsActivity :
|
class SettingsActivity :
|
||||||
AppCompatActivity(),
|
AppCompatActivity(),
|
||||||
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
|
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
|
||||||
@@ -147,7 +143,7 @@ class SettingsActivity :
|
|||||||
InputFilter { source, _, _, dest, _, _ ->
|
InputFilter { source, _, _, dest, _, _ ->
|
||||||
try {
|
try {
|
||||||
val input: Int = (dest.toString() + source.toString()).toInt()
|
val input: Int = (dest.toString() + source.toString()).toInt()
|
||||||
if (input in MIN_ITEMS_NUMBER..MAX_ITEMS_NUMBER) return@InputFilter null
|
if (input in 1..200) return@InputFilter null
|
||||||
} catch (nfe: NumberFormatException) {
|
} catch (nfe: NumberFormatException) {
|
||||||
Toast
|
Toast
|
||||||
.makeText(
|
.makeText(
|
||||||
@@ -240,7 +236,7 @@ class SettingsActivity :
|
|||||||
|
|
||||||
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener =
|
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
openUrl(AppSettingsService.BUG_URL)
|
openUrl(AppSettingsService.TRACKER_URL)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,60 +2,23 @@ package bou.amine.apps.readerforselfossv2.android.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.TypedValue
|
|
||||||
import androidx.annotation.AttrRes
|
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
|
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
|
||||||
|
|
||||||
fun Context.shareLink(
|
fun Context.shareLink(
|
||||||
itemUrl: String?,
|
itemUrl: String,
|
||||||
itemTitle: String,
|
itemTitle: String,
|
||||||
) {
|
) {
|
||||||
if (itemUrl.isUrlValid()) {
|
val sendIntent = Intent()
|
||||||
val sendIntent = Intent()
|
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
sendIntent.action = Intent.ACTION_SEND
|
||||||
sendIntent.action = Intent.ACTION_SEND
|
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl.toStringUriWithHttp())
|
||||||
sendIntent.putExtra(Intent.EXTRA_TEXT, itemUrl!!.toStringUriWithHttp())
|
sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle)
|
||||||
sendIntent.putExtra(Intent.EXTRA_SUBJECT, itemTitle)
|
sendIntent.type = "text/plain"
|
||||||
sendIntent.type = "text/plain"
|
startActivity(
|
||||||
startActivity(
|
Intent.createChooser(
|
||||||
Intent
|
sendIntent,
|
||||||
.createChooser(
|
getString(R.string.share),
|
||||||
sendIntent,
|
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||||
getString(R.string.share),
|
)
|
||||||
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ColorInt
|
|
||||||
fun Fragment.getColorFromAttr(
|
|
||||||
@AttrRes attrColor: Int,
|
|
||||||
resolveRefs: Boolean = true,
|
|
||||||
): Int {
|
|
||||||
val typedValue = TypedValue()
|
|
||||||
maybeIfContextWithLog { this.requireContext().theme.resolveAttribute(attrColor, typedValue, resolveRefs) }
|
|
||||||
return typedValue.data
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
fun Fragment.maybeIfContext(fn: (Context) -> Any): Any? {
|
|
||||||
try {
|
|
||||||
return fn(this.requireContext())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// Do nothing
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Fragment.maybeIfContextWithLog(fn: (Context) -> Any): Any? {
|
|
||||||
try {
|
|
||||||
return fn(this.requireContext())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.sendSilentlyWithAcraWithName("Fragment context issue...")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -59,5 +59,7 @@ class CircleImageView
|
|||||||
textView.text = text.toTextDrawableString()
|
textView.text = text.toTextDrawableString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun colorFromIdentifier(key: String): Int = colorScheme[abs(key.hashCode()) % colorScheme.size]
|
private fun colorFromIdentifier(key: String): Int {
|
||||||
|
return colorScheme[abs(key.hashCode()) % colorScheme.size]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,35 +15,33 @@ import android.widget.Toast
|
|||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.ReaderActivity
|
import bou.amine.apps.readerforselfossv2.android.ReaderActivity
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
|
||||||
fun Context.openItemUrl(
|
fun Context.openItemUrl(
|
||||||
currentItem: Int,
|
currentItem: Int,
|
||||||
linkDecoded: String?,
|
linkDecoded: String,
|
||||||
articleViewer: Boolean,
|
articleViewer: Boolean,
|
||||||
app: Activity,
|
app: Activity,
|
||||||
) {
|
) {
|
||||||
if (!linkDecoded.isUrlValid()) {
|
if (!linkDecoded.isUrlValid()) {
|
||||||
Toast
|
Toast.makeText(
|
||||||
.makeText(
|
this,
|
||||||
this,
|
this.getString(R.string.cant_open_invalid_url),
|
||||||
this.getString(R.string.cant_open_invalid_url),
|
Toast.LENGTH_LONG,
|
||||||
Toast.LENGTH_LONG,
|
).show()
|
||||||
).show()
|
|
||||||
} else {
|
} else {
|
||||||
if (articleViewer) {
|
if (articleViewer) {
|
||||||
val intent = Intent(this, ReaderActivity::class.java)
|
val intent = Intent(this, ReaderActivity::class.java)
|
||||||
intent.putExtra("currentItem", currentItem)
|
intent.putExtra("currentItem", currentItem)
|
||||||
app.startActivity(intent)
|
app.startActivity(intent)
|
||||||
} else {
|
} else {
|
||||||
this.openUrlInBrowserAsNewTask(linkDecoded!!)
|
this.openUrlInBrowserAsNewTask(linkDecoded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String?.isUrlValid(): Boolean =
|
fun String.isUrlValid(): Boolean = this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
|
||||||
!this.isEmptyOrNullOrNullString() && this!!.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
|
|
||||||
|
|
||||||
fun String.isBaseUrlInvalid(): Boolean {
|
fun String.isBaseUrlInvalid(): Boolean {
|
||||||
val baseUrl = this.toHttpUrlOrNull()
|
val baseUrl = this.toHttpUrlOrNull()
|
||||||
@@ -57,16 +55,14 @@ fun String.isBaseUrlInvalid(): Boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Context.openItemUrlInBrowserAsNewTask(i: SelfossModel.Item) {
|
fun Context.openItemUrlInBrowserAsNewTask(i: SelfossModel.Item) {
|
||||||
this.openUrlInBrowserAsNewTask(i.getLinkDecoded())
|
this.openUrlInBrowserAsNewTask(i.getLinkDecoded().toStringUriWithHttp())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.openUrlInBrowserAsNewTask(url: String?) {
|
fun Context.openUrlInBrowserAsNewTask(url: String) {
|
||||||
if (url.isUrlValid()) {
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
val intent = Intent(Intent.ACTION_VIEW)
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
intent.data = Uri.parse(url)
|
||||||
intent.data = Uri.parse(url)
|
this.mayBeStartActivity(intent)
|
||||||
this.mayBeStartActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.openUrlInBrowser(url: String) {
|
fun Context.openUrlInBrowser(url: String) {
|
||||||
@@ -75,7 +71,6 @@ fun Context.openUrlInBrowser(url: String) {
|
|||||||
this.mayBeStartActivity(intent)
|
this.mayBeStartActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
fun Context.mayBeStartActivity(intent: Intent) {
|
fun Context.mayBeStartActivity(intent: Intent) {
|
||||||
try {
|
try {
|
||||||
this.startActivity(intent)
|
this.startActivity(intent)
|
||||||
|
@@ -22,5 +22,5 @@ class AcraReportingAdministrator : ReportingAdministrator {
|
|||||||
context: Context,
|
context: Context,
|
||||||
config: CoreConfiguration,
|
config: CoreConfiguration,
|
||||||
crashReportData: CrashReportData,
|
crashReportData: CrashReportData,
|
||||||
): Boolean = crashReportData.get("BRAND") != "redroid" && !crashReportData.get("PHONE_MODEL").toString().startsWith("sdk_gphone")
|
): Boolean = crashReportData.get("BRAND") != "redroid"
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.utils.bottombar
|
package bou.amine.apps.readerforselfossv2.android.utils.bottombar
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import androidx.annotation.IdRes
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
|
||||||
import com.ashokvarma.bottomnavigation.TextBadgeItem
|
import com.ashokvarma.bottomnavigation.TextBadgeItem
|
||||||
import com.leinardi.android.speeddial.SpeedDialActionItem
|
|
||||||
import com.leinardi.android.speeddial.SpeedDialView
|
|
||||||
|
|
||||||
fun TextBadgeItem.removeBadge(): TextBadgeItem {
|
fun TextBadgeItem.removeBadge(): TextBadgeItem {
|
||||||
this.setText("")
|
this.setText("")
|
||||||
@@ -16,25 +9,3 @@ fun TextBadgeItem.removeBadge(): TextBadgeItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun TextBadgeItem.maybeShow(): TextBadgeItem = if (this.isHidden) this.show() else this
|
fun TextBadgeItem.maybeShow(): TextBadgeItem = if (this.isHidden) this.show() else this
|
||||||
|
|
||||||
@Suppress("detekt:LongParameterList")
|
|
||||||
fun SpeedDialView.addHomeMadeActionItem(
|
|
||||||
@IdRes actionId: Int,
|
|
||||||
actionIcon: Drawable,
|
|
||||||
@StringRes labelId: Int,
|
|
||||||
colorOnSurface: Int,
|
|
||||||
colorSurface: Int,
|
|
||||||
context: Context,
|
|
||||||
) {
|
|
||||||
this.addActionItem(
|
|
||||||
SpeedDialActionItem
|
|
||||||
.Builder(actionId, actionIcon)
|
|
||||||
.setFabBackgroundColor(context.resources.getColor(R.color.colorAccent))
|
|
||||||
.setFabImageTintColor(colorOnSurface)
|
|
||||||
.setLabel(context.getString(labelId))
|
|
||||||
.setLabelClickable(false)
|
|
||||||
.setLabelBackgroundColor(colorOnSurface)
|
|
||||||
.setLabelColor(colorSurface)
|
|
||||||
.create(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@@ -2,135 +2,40 @@ package bou.amine.apps.readerforselfossv2.android.utils.glide
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.webkit.WebView
|
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
|
import bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
|
||||||
import com.bumptech.glide.load.model.LazyHeaders
|
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.bumptech.glide.request.target.ViewTarget
|
|
||||||
import com.google.android.material.chip.Chip
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import kotlin.io.encoding.Base64
|
|
||||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
|
||||||
|
|
||||||
private const val PRELOAD_IMAGE_TIMEOUT = 10000
|
|
||||||
|
|
||||||
@Suppress("detekt:ReturnCount")
|
|
||||||
@OptIn(ExperimentalEncodingApi::class)
|
|
||||||
fun String.toGlideUrl(appSettingsService: AppSettingsService): Any { // GlideUrl Or String
|
|
||||||
if (this.isEmptyOrNullOrNullString()) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty()) {
|
|
||||||
val authString = "${appSettingsService.getBasicUserName()}:${appSettingsService.getBasicPassword()}"
|
|
||||||
val authBuf = Base64.encode(authString.toByteArray(Charsets.UTF_8))
|
|
||||||
|
|
||||||
return GlideUrl(
|
|
||||||
this,
|
|
||||||
LazyHeaders
|
|
||||||
.Builder()
|
|
||||||
.addHeader("Authorization", "Basic $authBuf")
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return GlideUrl(
|
|
||||||
this,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun WebView.getGlideImageForResource(
|
|
||||||
url: String,
|
|
||||||
appSettingsService: AppSettingsService,
|
|
||||||
) = Glide
|
|
||||||
.with(this)
|
|
||||||
.asBitmap()
|
|
||||||
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL))
|
|
||||||
.load(url.toGlideUrl(appSettingsService))
|
|
||||||
.submit()
|
|
||||||
.get()
|
|
||||||
|
|
||||||
fun Context.preloadImage(
|
|
||||||
url: String,
|
|
||||||
appSettingsService: AppSettingsService,
|
|
||||||
) = Glide
|
|
||||||
.with(this)
|
|
||||||
.asBitmap()
|
|
||||||
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(PRELOAD_IMAGE_TIMEOUT))
|
|
||||||
.load(url.toGlideUrl(appSettingsService))
|
|
||||||
.submit()
|
|
||||||
|
|
||||||
fun Context.imageIntoViewTarget(
|
|
||||||
url: String,
|
|
||||||
target: ViewTarget<Chip?, Drawable?>,
|
|
||||||
appSettingsService: AppSettingsService,
|
|
||||||
) = Glide
|
|
||||||
.with(this)
|
|
||||||
.load(url.toGlideUrl(appSettingsService))
|
|
||||||
.into(target)
|
|
||||||
|
|
||||||
fun Context.bitmapWithCache(
|
|
||||||
url: String,
|
|
||||||
iv: ImageView,
|
|
||||||
appSettingsService: AppSettingsService,
|
|
||||||
) = Glide
|
|
||||||
.with(this)
|
|
||||||
.asBitmap()
|
|
||||||
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL))
|
|
||||||
.load(url.toGlideUrl(appSettingsService))
|
|
||||||
.into(iv)
|
|
||||||
|
|
||||||
fun Context.bitmapCenterCrop(
|
fun Context.bitmapCenterCrop(
|
||||||
url: String,
|
url: String,
|
||||||
iv: ImageView,
|
iv: ImageView,
|
||||||
appSettingsService: AppSettingsService,
|
) = Glide.with(this)
|
||||||
) = Glide
|
|
||||||
.with(this)
|
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(url.toGlideUrl(appSettingsService))
|
.load(url)
|
||||||
.apply(RequestOptions.centerCropTransform())
|
.apply(RequestOptions.centerCropTransform())
|
||||||
.into(iv)
|
.into(iv)
|
||||||
|
|
||||||
fun Context.bitmapFitCenter(
|
|
||||||
url: String,
|
|
||||||
iv: ImageView,
|
|
||||||
appSettingsService: AppSettingsService,
|
|
||||||
) = Glide
|
|
||||||
.with(this)
|
|
||||||
.asBitmap()
|
|
||||||
.load(url.toGlideUrl(appSettingsService))
|
|
||||||
.apply(RequestOptions.fitCenterTransform())
|
|
||||||
.into(iv)
|
|
||||||
|
|
||||||
fun Context.circularDrawable(
|
fun Context.circularDrawable(
|
||||||
url: String,
|
url: String,
|
||||||
view: CircleImageView,
|
view: CircleImageView,
|
||||||
appSettingsService: AppSettingsService,
|
|
||||||
) {
|
) {
|
||||||
view.textView.text = ""
|
view.textView.text = ""
|
||||||
|
|
||||||
Glide
|
Glide.with(this)
|
||||||
.with(this)
|
.load(url)
|
||||||
.load(url.toGlideUrl(appSettingsService))
|
|
||||||
.into(view.imageView)
|
.into(view.imageView)
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val BITMAP_INPUT_STREAM_COMPRESSION_QUALITY = 80
|
|
||||||
|
|
||||||
fun getBitmapInputStream(
|
fun getBitmapInputStream(
|
||||||
bitmap: Bitmap,
|
bitmap: Bitmap,
|
||||||
compressFormat: Bitmap.CompressFormat,
|
compressFormat: Bitmap.CompressFormat,
|
||||||
): InputStream {
|
): InputStream {
|
||||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||||
bitmap.compress(compressFormat, BITMAP_INPUT_STREAM_COMPRESSION_QUALITY, byteArrayOutputStream)
|
bitmap.compress(compressFormat, 80, byteArrayOutputStream)
|
||||||
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
|
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
|
||||||
return ByteArrayInputStream(bitmapData)
|
return ByteArrayInputStream(bitmapData)
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.android.utils.network
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
|
import android.os.Build
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
|
||||||
lateinit var s: Snackbar
|
lateinit var s: Snackbar
|
||||||
@@ -10,13 +11,19 @@ lateinit var s: Snackbar
|
|||||||
fun isNetworkAccessible(context: Context): Boolean {
|
fun isNetworkAccessible(context: Context): Boolean {
|
||||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
|
||||||
val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) ?: return false
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
val network = connectivityManager.activeNetwork ?: return false
|
||||||
|
val networkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
|
||||||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
|
||||||
else -> false
|
else -> false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val network = connectivityManager.activeNetworkInfo ?: return false
|
||||||
|
return network.isConnectedOrConnecting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,30 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class AppViewModel(private val repository: Repository) : ViewModel() {
|
||||||
|
private val _networkAvailableProvider = MutableSharedFlow<Boolean>()
|
||||||
|
val networkAvailableProvider = _networkAvailableProvider.asSharedFlow()
|
||||||
|
private var wasConnected = true
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -71,13 +71,35 @@
|
|||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
<com.leinardi.android.speeddial.SpeedDialView
|
<FrameLayout
|
||||||
android:id="@+id/speedDial"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="start|bottom|end"
|
||||||
app:layout_behavior="@string/speeddial_scrolling_view_snackbar_behavior"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:sdMainFabClosedSrc="@drawable/ic_add_white_24dp" />
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent">
|
||||||
|
|
||||||
|
<com.github.rubensousa.floatingtoolbar.FloatingToolbar
|
||||||
|
android:id="@+id/floatingToolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
app:floatingMenu="@menu/reader_toolbar" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/fab"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end|bottom"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
android:src="@drawable/ic_add_white_24dp"
|
||||||
|
app:backgroundTint="?attr/colorAccent"
|
||||||
|
app:fabSize="mini"
|
||||||
|
app:rippleColor="?attr/colorAccentDark" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
@@ -97,5 +119,4 @@
|
|||||||
android:progressTint="?attr/colorAccent" />
|
android:progressTint="?attr/colorAccent" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
23
androidApp/src/main/res/menu/reader_toolbar.xml
Normal file
23
androidApp/src/main/res/menu/reader_toolbar.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/unread_action"
|
||||||
|
android:icon="@drawable/ic_baseline_white_eye_24dp"
|
||||||
|
android:title="@string/unmark"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/open_action"
|
||||||
|
android:icon="@drawable/ic_open_in_browser_white_24dp"
|
||||||
|
android:title="@string/reader_action_open"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/share_action"
|
||||||
|
android:icon="@drawable/ic_share_white_24dp"
|
||||||
|
android:title="@string/reader_action_share"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
</menu>
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"No s'han llegit totes les publicacions"</string>
|
<string name="all_posts_not_read">"No s'han llegit totes les publicacions"</string>
|
||||||
<string name="all_posts_read">"S'han llegit totes les publicacions"</string>
|
<string name="all_posts_read">"S'han llegit totes les publicacions"</string>
|
||||||
<string name="undo_string">"Desfés"</string>
|
<string name="undo_string">"Desfés"</string>
|
||||||
|
<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_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_create_source">"No es pot crear la font."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</string>
|
<string name="settings_reader_font">Reader font</string>
|
||||||
|
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||||
|
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||||
|
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||||
<string name="remove_source">Remove source</string>
|
<string name="remove_source">Remove source</string>
|
||||||
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
<string name="mode_system">Follow the system setting</string>
|
<string name="mode_system">Follow the system setting</string>
|
||||||
<string name="mode_light">Light mode</string>
|
<string name="mode_light">Light mode</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"Quant a"</string>
|
<string name="action_about">"Quant a"</string>
|
||||||
<string name="marked_as_read">"Element llegit"</string>
|
<string name="marked_as_read">"Element llegit"</string>
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"Nicht alle Beiträge wurden gelesen"</string>
|
<string name="all_posts_not_read">"Nicht alle Beiträge wurden gelesen"</string>
|
||||||
<string name="all_posts_read">"Alle Beiträge wurden gelesen"</string>
|
<string name="all_posts_read">"Alle Beiträge wurden gelesen"</string>
|
||||||
<string name="undo_string">"Rückgängig"</string>
|
<string name="undo_string">"Rückgängig"</string>
|
||||||
|
<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_get_sources">"Quellen können nicht abgerufen werden."</string>
|
||||||
<string name="cant_create_source">"Quelle kann nicht gespeichert werden."</string>
|
<string name="cant_create_source">"Quelle kann nicht gespeichert werden."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Fehler beim Laden der Spouts-Liste aufgrund von Netzwerkproblemen."</string>
|
<string name="cant_get_spouts_no_network">"Fehler beim Laden der Spouts-Liste aufgrund von Netzwerkproblemen."</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">Linksbündig</string>
|
<string name="reader_text_align_left">Linksbündig</string>
|
||||||
<string name="reader_text_align_justify">Blocksatz</string>
|
<string name="reader_text_align_justify">Blocksatz</string>
|
||||||
<string name="settings_reader_font">Schriftgröße im Lesemodus</string>
|
<string name="settings_reader_font">Schriftgröße im Lesemodus</string>
|
||||||
|
<string name="reader_static_bar_title">Statische untere Leiste im Lesemodus</string>
|
||||||
|
<string name="reader_static_bar_on">Die untere Leiste wird dauerhaft angezeigt</string>
|
||||||
|
<string name="reader_static_bar_off">Die untere Leiste kann über einen schwebenden Button angezeigt werden</string>
|
||||||
<string name="remove_source">Quelle entfernen</string>
|
<string name="remove_source">Quelle entfernen</string>
|
||||||
|
<string name="pref_theme_title">Heller/Dunkler Modus</string>
|
||||||
<string name="mode_dark">Dunkler Modus</string>
|
<string name="mode_dark">Dunkler Modus</string>
|
||||||
<string name="mode_system">Systemeinstellungen übernehmen</string>
|
<string name="mode_system">Systemeinstellungen übernehmen</string>
|
||||||
<string name="mode_light">Heller Modus</string>
|
<string name="mode_light">Heller Modus</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"Über"</string>
|
<string name="action_about">"Über"</string>
|
||||||
<string name="marked_as_read">"Artikel gelesen"</string>
|
<string name="marked_as_read">"Artikel gelesen"</string>
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"No todas las publicaciones fueron leídas"</string>
|
<string name="all_posts_not_read">"No todas las publicaciones fueron leídas"</string>
|
||||||
<string name="all_posts_read">"Todas las publicaciones fueron leídas"</string>
|
<string name="all_posts_read">"Todas las publicaciones fueron leídas"</string>
|
||||||
<string name="undo_string">"Deshacer"</string>
|
<string name="undo_string">"Deshacer"</string>
|
||||||
|
<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_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_create_source">"No se puede crear la fuente."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">Alinear a la izquierda</string>
|
<string name="reader_text_align_left">Alinear a la izquierda</string>
|
||||||
<string name="reader_text_align_justify">Justificado</string>
|
<string name="reader_text_align_justify">Justificado</string>
|
||||||
<string name="settings_reader_font">Modo lectura</string>
|
<string name="settings_reader_font">Modo lectura</string>
|
||||||
|
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||||
|
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||||
|
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||||
<string name="remove_source">Remove source</string>
|
<string name="remove_source">Remove source</string>
|
||||||
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
<string name="mode_system">Follow the system setting</string>
|
<string name="mode_system">Follow the system setting</string>
|
||||||
<string name="mode_light">Light mode</string>
|
<string name="mode_light">Light mode</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"Acerca de"</string>
|
<string name="action_about">"Acerca de"</string>
|
||||||
<string name="marked_as_read">"Artículo leído"</string>
|
<string name="marked_as_read">"Artículo leído"</string>
|
||||||
<string name="marked_as_unread">"Artículo no leído"</string>
|
<string name="marked_as_unread">"Artículo no leído"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"Tous les posts n'ont pas été lus"</string>
|
<string name="all_posts_not_read">"Tous les posts n'ont pas été lus"</string>
|
||||||
<string name="all_posts_read">"Tous les posts sont lus"</string>
|
<string name="all_posts_read">"Tous les posts sont lus"</string>
|
||||||
<string name="undo_string">"Annuler"</string>
|
<string name="undo_string">"Annuler"</string>
|
||||||
|
<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_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_create_source">"Impossible de créer la source."</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_no_network">"Impossible d'obtenir la liste des spouts en raison d'un problème de réseau."</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">Aligner à gauche</string>
|
<string name="reader_text_align_left">Aligner à gauche</string>
|
||||||
<string name="reader_text_align_justify">Justifier le texte</string>
|
<string name="reader_text_align_justify">Justifier le texte</string>
|
||||||
<string name="settings_reader_font">Police du lecteur d\'articles</string>
|
<string name="settings_reader_font">Police du lecteur d\'articles</string>
|
||||||
|
<string name="reader_static_bar_title">Barre statique pour le visionneur d\'articles</string>
|
||||||
|
<string name="reader_static_bar_on">La barre sera affichée</string>
|
||||||
|
<string name="reader_static_bar_off">La barre sera affichée grâce au bouton</string>
|
||||||
<string name="remove_source">Supprimer la source</string>
|
<string name="remove_source">Supprimer la source</string>
|
||||||
|
<string name="pref_theme_title">Thème Clair/Sombre</string>
|
||||||
<string name="mode_dark">Thème sombre</string>
|
<string name="mode_dark">Thème sombre</string>
|
||||||
<string name="mode_system">Utiliser les paramètres système</string>
|
<string name="mode_system">Utiliser les paramètres système</string>
|
||||||
<string name="mode_light">Thème clair</string>
|
<string name="mode_light">Thème clair</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"À propos"</string>
|
<string name="action_about">"À propos"</string>
|
||||||
<string name="marked_as_read">"Marqué comme lu"</string>
|
<string name="marked_as_read">"Marqué comme lu"</string>
|
||||||
<string name="marked_as_unread">"Marqué comme non lu"</string>
|
<string name="marked_as_unread">"Marqué comme non lu"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"Non se leron todas as publicacións"</string>
|
<string name="all_posts_not_read">"Non se leron todas as publicacións"</string>
|
||||||
<string name="all_posts_read">"Leronse todas as publicacións"</string>
|
<string name="all_posts_read">"Leronse todas as publicacións"</string>
|
||||||
<string name="undo_string">"Desfacer"</string>
|
<string name="undo_string">"Desfacer"</string>
|
||||||
|
<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_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_create_source">"Non se pode crear unha fonte."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Non se pode obter a lista de spouts por mor dun erro de rede."</string>
|
<string name="cant_get_spouts_no_network">"Non se pode obter a lista de spouts por mor dun erro de rede."</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">Aliñar á esquerda</string>
|
<string name="reader_text_align_left">Aliñar á esquerda</string>
|
||||||
<string name="reader_text_align_justify">Xustificado</string>
|
<string name="reader_text_align_justify">Xustificado</string>
|
||||||
<string name="settings_reader_font">Modo lector</string>
|
<string name="settings_reader_font">Modo lector</string>
|
||||||
|
<string name="reader_static_bar_title">Barra inferior estática na vista de artigos</string>
|
||||||
|
<string name="reader_static_bar_on">A barra inferior mostrarase sempre</string>
|
||||||
|
<string name="reader_static_bar_off">A barra inferior pode mostrarse a través do botón flotante</string>
|
||||||
<string name="remove_source">Eliminar fonte</string>
|
<string name="remove_source">Eliminar fonte</string>
|
||||||
|
<string name="pref_theme_title">Modo Claro/Escuro</string>
|
||||||
<string name="mode_dark">Modo escuro</string>
|
<string name="mode_dark">Modo escuro</string>
|
||||||
<string name="mode_system">Seguir axustes do sistema</string>
|
<string name="mode_system">Seguir axustes do sistema</string>
|
||||||
<string name="mode_light">Modo claro</string>
|
<string name="mode_light">Modo claro</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"Acerca de"</string>
|
<string name="action_about">"Acerca de"</string>
|
||||||
<string name="marked_as_read">"Elemento lido"</string>
|
<string name="marked_as_read">"Elemento lido"</string>
|
||||||
<string name="marked_as_unread">"Elemento non lido"</string>
|
<string name="marked_as_unread">"Elemento non lido"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"Semua pos belum dibaca"</string>
|
<string name="all_posts_not_read">"Semua pos belum dibaca"</string>
|
||||||
<string name="all_posts_read">"Semua pos sudah dibaca"</string>
|
<string name="all_posts_read">"Semua pos sudah dibaca"</string>
|
||||||
<string name="undo_string">"Urung"</string>
|
<string name="undo_string">"Urung"</string>
|
||||||
|
<string name="addStringNoUrl">"Masuk untuk menambah sumber."</string>
|
||||||
<string name="cant_get_sources">"Tidak bisa mendapatkan daftar 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_create_source">"Tidak dapat membuat sumber."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</string>
|
<string name="settings_reader_font">Reader font</string>
|
||||||
|
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||||
|
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||||
|
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||||
<string name="remove_source">Remove source</string>
|
<string name="remove_source">Remove source</string>
|
||||||
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
<string name="mode_system">Follow the system setting</string>
|
<string name="mode_system">Follow the system setting</string>
|
||||||
<string name="mode_light">Light mode</string>
|
<string name="mode_light">Light mode</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"Tentang"</string>
|
<string name="action_about">"Tentang"</string>
|
||||||
<string name="marked_as_read">"Membaca item"</string>
|
<string name="marked_as_read">"Membaca item"</string>
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"All posts weren't read"</string>
|
<string name="all_posts_not_read">"All posts weren't read"</string>
|
||||||
<string name="all_posts_read">"Tutti i messaggi sono stati letti"</string>
|
<string name="all_posts_read">"Tutti i messaggi sono stati letti"</string>
|
||||||
<string name="undo_string">"Annulla"</string>
|
<string name="undo_string">"Annulla"</string>
|
||||||
|
<string name="addStringNoUrl">"Autenticati per aggiungere fonti."</string>
|
||||||
<string name="cant_get_sources">"Can't get sources list."</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_create_source">"Can't create source."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</string>
|
<string name="settings_reader_font">Reader font</string>
|
||||||
|
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||||
|
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||||
|
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||||
<string name="remove_source">Remove source</string>
|
<string name="remove_source">Remove source</string>
|
||||||
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
<string name="mode_system">Follow the system setting</string>
|
<string name="mode_system">Follow the system setting</string>
|
||||||
<string name="mode_light">Light mode</string>
|
<string name="mode_light">Light mode</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"Informazioni"</string>
|
<string name="action_about">"Informazioni"</string>
|
||||||
<string name="marked_as_read">"Articolo letto"</string>
|
<string name="marked_as_read">"Articolo letto"</string>
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"모든 게시물을 읽지 않았습니다."</string>
|
<string name="all_posts_not_read">"모든 게시물을 읽지 않았습니다."</string>
|
||||||
<string name="all_posts_read">"모든 게시물을 읽었습니다."</string>
|
<string name="all_posts_read">"모든 게시물을 읽었습니다."</string>
|
||||||
<string name="undo_string">"실행 취소"</string>
|
<string name="undo_string">"실행 취소"</string>
|
||||||
|
<string name="addStringNoUrl">"로그인 소스를 추가 해야 합니다."</string>
|
||||||
<string name="cant_get_sources">"소스 리스트를 얻을 수 없습니다."</string>
|
<string name="cant_get_sources">"소스 리스트를 얻을 수 없습니다."</string>
|
||||||
<string name="cant_create_source">"소스를 만들 수 없습니다."</string>
|
<string name="cant_create_source">"소스를 만들 수 없습니다."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</string>
|
<string name="settings_reader_font">Reader font</string>
|
||||||
|
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||||
|
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||||
|
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||||
<string name="remove_source">Remove source</string>
|
<string name="remove_source">Remove source</string>
|
||||||
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
<string name="mode_system">Follow the system setting</string>
|
<string name="mode_system">Follow the system setting</string>
|
||||||
<string name="mode_light">Light mode</string>
|
<string name="mode_light">Light mode</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"정보"</string>
|
<string name="action_about">"정보"</string>
|
||||||
<string name="marked_as_read">"항목 읽기"</string>
|
<string name="marked_as_read">"항목 읽기"</string>
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -23,7 +23,7 @@
|
|||||||
<string name="wrong_infos">"Controleer de gegevens nogmaals."</string>
|
<string name="wrong_infos">"Controleer de gegevens nogmaals."</string>
|
||||||
<string name="all_posts_not_read">"Fout bij markeren als gelezen"</string>
|
<string name="all_posts_not_read">"Fout bij markeren als gelezen"</string>
|
||||||
<string name="all_posts_read">"Alle artikelen gemarkeerd als gelezen"</string>
|
<string name="all_posts_read">"Alle artikelen gemarkeerd als gelezen"</string>
|
||||||
<string name="undo_string">"Ongedaan maken"</string>
|
<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_get_sources">"Kan de lijst met bronnen niet ophalen"</string>
|
||||||
<string name="cant_create_source">"Kan bron niet creëeren"</string>
|
<string name="cant_create_source">"Kan bron niet creëeren"</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
@@ -105,7 +105,11 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</string>
|
<string name="settings_reader_font">Reader font</string>
|
||||||
|
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||||
|
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||||
|
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||||
<string name="remove_source">Remove source</string>
|
<string name="remove_source">Remove source</string>
|
||||||
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
<string name="mode_system">Follow the system setting</string>
|
<string name="mode_system">Follow the system setting</string>
|
||||||
<string name="mode_light">Light mode</string>
|
<string name="mode_light">Light mode</string>
|
||||||
@@ -127,6 +131,5 @@
|
|||||||
<string name="action_about">"Over"</string>
|
<string name="action_about">"Over"</string>
|
||||||
<string name="marked_as_read">"Artikel gelezen"</string>
|
<string name="marked_as_read">"Artikel gelezen"</string>
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
<string name="undo_string">"Ongedaan maken"</string>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
</resources>
|
||||||
</resources>
|
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"Nenhum post foi lido"</string>
|
<string name="all_posts_not_read">"Nenhum post foi lido"</string>
|
||||||
<string name="all_posts_read">"Todos os posts foram lidos"</string>
|
<string name="all_posts_read">"Todos os posts foram lidos"</string>
|
||||||
<string name="undo_string">"Desfazer"</string>
|
<string name="undo_string">"Desfazer"</string>
|
||||||
|
<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_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_create_source">"Não é possível criar fonte."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</string>
|
<string name="settings_reader_font">Reader font</string>
|
||||||
|
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||||
|
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||||
|
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||||
<string name="remove_source">Remove source</string>
|
<string name="remove_source">Remove source</string>
|
||||||
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
<string name="mode_system">Follow the system setting</string>
|
<string name="mode_system">Follow the system setting</string>
|
||||||
<string name="mode_light">Light mode</string>
|
<string name="mode_light">Light mode</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"Sobre"</string>
|
<string name="action_about">"Sobre"</string>
|
||||||
<string name="marked_as_read">"Item lido"</string>
|
<string name="marked_as_read">"Item lido"</string>
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"Todas as postagens não foram lidas"</string>
|
<string name="all_posts_not_read">"Todas as postagens não foram lidas"</string>
|
||||||
<string name="all_posts_read">"Todas as postagens foram lidas"</string>
|
<string name="all_posts_read">"Todas as postagens foram lidas"</string>
|
||||||
<string name="undo_string">"Desfazer"</string>
|
<string name="undo_string">"Desfazer"</string>
|
||||||
|
<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_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_create_source">"Não é possível criar a fonte."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</string>
|
<string name="settings_reader_font">Reader font</string>
|
||||||
|
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||||
|
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||||
|
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||||
<string name="remove_source">Remove source</string>
|
<string name="remove_source">Remove source</string>
|
||||||
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
<string name="mode_system">Follow the system setting</string>
|
<string name="mode_system">Follow the system setting</string>
|
||||||
<string name="mode_light">Light mode</string>
|
<string name="mode_light">Light mode</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"Sobre"</string>
|
<string name="action_about">"Sobre"</string>
|
||||||
<string name="marked_as_read">"Item lido"</string>
|
<string name="marked_as_read">"Item lido"</string>
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"All posts weren't read"</string>
|
<string name="all_posts_not_read">"All posts weren't read"</string>
|
||||||
<string name="all_posts_read">"All posts were read"</string>
|
<string name="all_posts_read">"All posts were read"</string>
|
||||||
<string name="undo_string">"Undo"</string>
|
<string name="undo_string">"Undo"</string>
|
||||||
|
<string name="addStringNoUrl">"Log in to add sources."</string>
|
||||||
<string name="cant_get_sources">"Can't get sources list."</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_create_source">"Can't create source."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</string>
|
<string name="settings_reader_font">Reader font</string>
|
||||||
|
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||||
|
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||||
|
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||||
<string name="remove_source">Remove source</string>
|
<string name="remove_source">Remove source</string>
|
||||||
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
<string name="mode_system">Follow the system setting</string>
|
<string name="mode_system">Follow the system setting</string>
|
||||||
<string name="mode_light">Light mode</string>
|
<string name="mode_light">Light mode</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"මේ ගැන"</string>
|
<string name="action_about">"මේ ගැන"</string>
|
||||||
<string name="marked_as_read">"Item read"</string>
|
<string name="marked_as_read">"Item read"</string>
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"Tüm mesajlar okunmadı"</string>
|
<string name="all_posts_not_read">"Tüm mesajlar okunmadı"</string>
|
||||||
<string name="all_posts_read">"Tüm mesajlar okundu"</string>
|
<string name="all_posts_read">"Tüm mesajlar okundu"</string>
|
||||||
<string name="undo_string">"Geri al"</string>
|
<string name="undo_string">"Geri al"</string>
|
||||||
|
<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_get_sources">"Kaynakları listesi alınamıyor."</string>
|
||||||
<string name="cant_create_source">"Kaynak oluşturulamıyor."</string>
|
<string name="cant_create_source">"Kaynak oluşturulamıyor."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</string>
|
<string name="settings_reader_font">Reader font</string>
|
||||||
|
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||||
|
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||||
|
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||||
<string name="remove_source">Remove source</string>
|
<string name="remove_source">Remove source</string>
|
||||||
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
<string name="mode_system">Follow the system setting</string>
|
<string name="mode_system">Follow the system setting</string>
|
||||||
<string name="mode_light">Light mode</string>
|
<string name="mode_light">Light mode</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"Hakkında"</string>
|
<string name="action_about">"Hakkında"</string>
|
||||||
<string name="marked_as_read">"Öğeleri oku"</string>
|
<string name="marked_as_read">"Öğeleri oku"</string>
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"所有帖子都未读"</string>
|
<string name="all_posts_not_read">"所有帖子都未读"</string>
|
||||||
<string name="all_posts_read">"所有帖子已读"</string>
|
<string name="all_posts_read">"所有帖子已读"</string>
|
||||||
<string name="undo_string">"撤销"</string>
|
<string name="undo_string">"撤销"</string>
|
||||||
|
<string name="addStringNoUrl">"登录以添加数据源。"</string>
|
||||||
<string name="cant_get_sources">"无法获取数据列表。"</string>
|
<string name="cant_get_sources">"无法获取数据列表。"</string>
|
||||||
<string name="cant_create_source">"无法创建源数据。"</string>
|
<string name="cant_create_source">"无法创建源数据。"</string>
|
||||||
<string name="cant_get_spouts_no_network">"由于网络问题,无法获取 spouts 列表。"</string>
|
<string name="cant_get_spouts_no_network">"由于网络问题,无法获取 spouts 列表。"</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">左对齐</string>
|
<string name="reader_text_align_left">左对齐</string>
|
||||||
<string name="reader_text_align_justify">左右对齐</string>
|
<string name="reader_text_align_justify">左右对齐</string>
|
||||||
<string name="settings_reader_font">阅读器字体</string>
|
<string name="settings_reader_font">阅读器字体</string>
|
||||||
|
<string name="reader_static_bar_title">文章查看器中的静态底部栏</string>
|
||||||
|
<string name="reader_static_bar_on">底部栏将始终显示</string>
|
||||||
|
<string name="reader_static_bar_off">底部栏可以通过浮动按钮显示</string>
|
||||||
<string name="remove_source">删除源</string>
|
<string name="remove_source">删除源</string>
|
||||||
|
<string name="pref_theme_title">浅色/深色模式</string>
|
||||||
<string name="mode_dark">深色模式</string>
|
<string name="mode_dark">深色模式</string>
|
||||||
<string name="mode_system">遵循系统设置</string>
|
<string name="mode_system">遵循系统设置</string>
|
||||||
<string name="mode_light">浅色模式</string>
|
<string name="mode_light">浅色模式</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"关于我们"</string>
|
<string name="action_about">"关于我们"</string>
|
||||||
<string name="marked_as_read">"已读"</string>
|
<string name="marked_as_read">"已读"</string>
|
||||||
<string name="marked_as_unread">"未读条目"</string>
|
<string name="marked_as_unread">"未读条目"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -24,6 +24,7 @@
|
|||||||
<string name="all_posts_not_read">"所有帖子都未读"</string>
|
<string name="all_posts_not_read">"所有帖子都未读"</string>
|
||||||
<string name="all_posts_read">"所有帖子已读"</string>
|
<string name="all_posts_read">"所有帖子已读"</string>
|
||||||
<string name="undo_string">"撤销"</string>
|
<string name="undo_string">"撤销"</string>
|
||||||
|
<string name="addStringNoUrl">"登录以添加数据源。"</string>
|
||||||
<string name="cant_get_sources">"无法获取数据列表。"</string>
|
<string name="cant_get_sources">"无法获取数据列表。"</string>
|
||||||
<string name="cant_create_source">"无法创建源数据。"</string>
|
<string name="cant_create_source">"无法创建源数据。"</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
@@ -105,7 +106,11 @@
|
|||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Align left</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Justify</string>
|
||||||
<string name="settings_reader_font">Reader font</string>
|
<string name="settings_reader_font">Reader font</string>
|
||||||
|
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||||
|
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||||
|
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||||
<string name="remove_source">Remove source</string>
|
<string name="remove_source">Remove source</string>
|
||||||
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
<string name="mode_system">Follow the system setting</string>
|
<string name="mode_system">Follow the system setting</string>
|
||||||
<string name="mode_light">Light mode</string>
|
<string name="mode_light">Light mode</string>
|
||||||
@@ -127,6 +132,4 @@
|
|||||||
<string name="action_about">"关于我们"</string>
|
<string name="action_about">"关于我们"</string>
|
||||||
<string name="marked_as_read">"已读"</string>
|
<string name="marked_as_read">"已读"</string>
|
||||||
<string name="marked_as_unread">"未讀項目"</string>
|
<string name="marked_as_unread">"未讀項目"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<item name="unread_action" type="id" />
|
|
||||||
<item name="open_action" type="id" />
|
|
||||||
<item name="share_action" type="id" />
|
|
||||||
</resources>
|
|
@@ -23,6 +23,7 @@
|
|||||||
<string name="all_posts_not_read">"All posts weren't read"</string>
|
<string name="all_posts_not_read">"All posts weren't read"</string>
|
||||||
<string name="all_posts_read">"All posts were read"</string>
|
<string name="all_posts_read">"All posts were read"</string>
|
||||||
<string name="undo_string">"Undo"</string>
|
<string name="undo_string">"Undo"</string>
|
||||||
|
<string name="addStringNoUrl">"Log in to add sources."</string>
|
||||||
<string name="cant_get_sources">"Can't get sources list."</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_create_source">"Can't create source."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
@@ -107,7 +108,11 @@
|
|||||||
<string name="source_code_pro_font_id" translatable="false">source_code_pro_medium</string>
|
<string name="source_code_pro_font_id" translatable="false">source_code_pro_medium</string>
|
||||||
<string name="open_sans_font_id" translatable="false">open_sans</string>
|
<string name="open_sans_font_id" translatable="false">open_sans</string>
|
||||||
<string name="roboto_font_id" translatable="false">roboto</string>
|
<string name="roboto_font_id" translatable="false">roboto</string>
|
||||||
|
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
||||||
|
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
||||||
|
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
||||||
<string name="remove_source">Remove source</string>
|
<string name="remove_source">Remove source</string>
|
||||||
|
<string name="pref_theme_title">Light/Dark mode</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dark mode</string>
|
||||||
<string name="mode_system">Follow the system setting</string>
|
<string name="mode_system">Follow the system setting</string>
|
||||||
<string name="mode_light">Light mode</string>
|
<string name="mode_light">Light mode</string>
|
||||||
@@ -129,6 +134,4 @@
|
|||||||
<string name="action_about">"About"</string>
|
<string name="action_about">"About"</string>
|
||||||
<string name="marked_as_read">"Item read"</string>
|
<string name="marked_as_read">"Item read"</string>
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
<string name="confirm_delete_title">Confirm Deletion</string>
|
</resources>
|
||||||
<string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string>
|
|
||||||
</resources>
|
|
@@ -30,6 +30,14 @@
|
|||||||
android:summaryOn="@string/pref_article_viewer_on"
|
android:summaryOn="@string/pref_article_viewer_on"
|
||||||
android:title="@string/pref_article_viewer_title"
|
android:title="@string/pref_article_viewer_title"
|
||||||
app:iconSpaceReserved="false"/>
|
app:iconSpaceReserved="false"/>
|
||||||
|
<SwitchPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:dependency="prefer_article_viewer"
|
||||||
|
android:key="reader_static_bar"
|
||||||
|
android:summaryOff="@string/reader_static_bar_off"
|
||||||
|
android:summaryOn="@string/reader_static_bar_on"
|
||||||
|
android:title="@string/reader_static_bar_title"
|
||||||
|
app:iconSpaceReserved="false"/>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:title="@string/pref_general_category_displaying">
|
android:title="@string/pref_general_category_displaying">
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
@file:Suppress("detekt:LargeClass")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.tests.repository
|
package bou.amine.apps.readerforselfossv2.tests.repository
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
||||||
@@ -11,7 +9,6 @@ import bou.amine.apps.readerforselfossv2.model.SuccessResponse
|
|||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import bou.amine.apps.readerforselfossv2.service.ConnectivityService
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||||
import bou.amine.apps.readerforselfossv2.utils.toView
|
import bou.amine.apps.readerforselfossv2.utils.toView
|
||||||
import io.mockk.clearAllMocks
|
import io.mockk.clearAllMocks
|
||||||
@@ -25,6 +22,7 @@ import junit.framework.TestCase.assertFalse
|
|||||||
import junit.framework.TestCase.assertNotSame
|
import junit.framework.TestCase.assertNotSame
|
||||||
import junit.framework.TestCase.assertSame
|
import junit.framework.TestCase.assertSame
|
||||||
import junit.framework.TestCase.assertTrue
|
import junit.framework.TestCase.assertTrue
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertNotEquals
|
import org.junit.Assert.assertNotEquals
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -52,12 +50,16 @@ class RepositoryTest {
|
|||||||
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
||||||
private val appSettingsService = mockk<AppSettingsService>()
|
private val appSettingsService = mockk<AppSettingsService>()
|
||||||
private val api = mockk<SelfossApi>()
|
private val api = mockk<SelfossApi>()
|
||||||
private val connectivityService = mockk<ConnectivityService>()
|
|
||||||
private lateinit var repository: Repository
|
private lateinit var repository: Repository
|
||||||
|
|
||||||
private fun initializeRepository(isNetworkAvailable: Boolean = true) {
|
private fun initializeRepository(
|
||||||
every { connectivityService.isNetworkAvailable() } returns isNetworkAvailable
|
isConnectionAvailable: MutableStateFlow<Boolean> =
|
||||||
repository = Repository(api, appSettingsService, connectivityService, db)
|
MutableStateFlow(
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
repository = Repository(api, appSettingsService, isConnectionAvailable, db)
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
repository.updateApiInformation()
|
repository.updateApiInformation()
|
||||||
@@ -107,7 +109,7 @@ class RepositoryTest {
|
|||||||
fun instantiate_repository_without_api_version() {
|
fun instantiate_repository_without_api_version() {
|
||||||
every { appSettingsService.getApiVersion() } returns -1
|
every { appSettingsService.getApiVersion() } returns -1
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
|
|
||||||
coVerify(exactly = 0) { api.apiInformation() }
|
coVerify(exactly = 0) { api.apiInformation() }
|
||||||
coVerify(exactly = 0) { api.stats() }
|
coVerify(exactly = 0) { api.stats() }
|
||||||
@@ -284,7 +286,7 @@ class RepositoryTest {
|
|||||||
fun get_newer_items_without_connectivity() {
|
fun get_newer_items_without_connectivity() {
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
runBlocking {
|
runBlocking {
|
||||||
repository.getNewerItems()
|
repository.getNewerItems()
|
||||||
}
|
}
|
||||||
@@ -311,7 +313,7 @@ class RepositoryTest {
|
|||||||
|
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
repository.setTagFilter(SelfossModel.Tag("Test", "red", 3))
|
repository.setTagFilter(SelfossModel.Tag("Test", "red", 3))
|
||||||
runBlocking {
|
runBlocking {
|
||||||
repository.getNewerItems()
|
repository.getNewerItems()
|
||||||
@@ -339,7 +341,7 @@ class RepositoryTest {
|
|||||||
|
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
repository.setSourceFilter(
|
repository.setSourceFilter(
|
||||||
SelfossModel.SourceDetail(
|
SelfossModel.SourceDetail(
|
||||||
1,
|
1,
|
||||||
@@ -454,7 +456,7 @@ class RepositoryTest {
|
|||||||
|
|
||||||
var success: Boolean
|
var success: Boolean
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
runBlocking {
|
runBlocking {
|
||||||
success = repository.reloadBadges()
|
success = repository.reloadBadges()
|
||||||
}
|
}
|
||||||
@@ -474,7 +476,7 @@ class RepositoryTest {
|
|||||||
|
|
||||||
var success: Boolean
|
var success: Boolean
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
runBlocking {
|
runBlocking {
|
||||||
success = repository.reloadBadges()
|
success = repository.reloadBadges()
|
||||||
}
|
}
|
||||||
@@ -569,7 +571,7 @@ class RepositoryTest {
|
|||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns true
|
every { appSettingsService.isUpdateSourcesEnabled() } returns true
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
var testTags: List<SelfossModel.Tag>
|
var testTags: List<SelfossModel.Tag>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testTags = repository.getTags()
|
testTags = repository.getTags()
|
||||||
@@ -587,7 +589,7 @@ class RepositoryTest {
|
|||||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns true
|
every { appSettingsService.isUpdateSourcesEnabled() } returns true
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
var testTags: List<SelfossModel.Tag>
|
var testTags: List<SelfossModel.Tag>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testTags = repository.getTags()
|
testTags = repository.getTags()
|
||||||
@@ -604,7 +606,7 @@ class RepositoryTest {
|
|||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
var testTags: List<SelfossModel.Tag>
|
var testTags: List<SelfossModel.Tag>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testTags = repository.getTags()
|
testTags = repository.getTags()
|
||||||
@@ -622,7 +624,7 @@ class RepositoryTest {
|
|||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
var testTags: List<SelfossModel.Tag>
|
var testTags: List<SelfossModel.Tag>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testTags = repository.getTags()
|
testTags = repository.getTags()
|
||||||
@@ -772,7 +774,7 @@ class RepositoryTest {
|
|||||||
@Test
|
@Test
|
||||||
fun get_sources_without_connection() {
|
fun get_sources_without_connection() {
|
||||||
val (_, sourcesDB) = prepareSources()
|
val (_, sourcesDB) = prepareSources()
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
var testSources: List<SelfossModel.Source>
|
var testSources: List<SelfossModel.Source>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testSources = repository.getSourcesDetails()
|
testSources = repository.getSourcesDetails()
|
||||||
@@ -789,7 +791,7 @@ class RepositoryTest {
|
|||||||
|
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns true
|
every { appSettingsService.isUpdateSourcesEnabled() } returns true
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
var testSources: List<SelfossModel.Source>
|
var testSources: List<SelfossModel.Source>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testSources = repository.getSourcesDetails()
|
testSources = repository.getSourcesDetails()
|
||||||
@@ -806,7 +808,7 @@ class RepositoryTest {
|
|||||||
|
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns true
|
every { appSettingsService.isItemCachingEnabled() } returns true
|
||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
var testSources: List<SelfossModel.Source>
|
var testSources: List<SelfossModel.Source>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testSources = repository.getSourcesDetails()
|
testSources = repository.getSourcesDetails()
|
||||||
@@ -823,7 +825,7 @@ class RepositoryTest {
|
|||||||
|
|
||||||
every { appSettingsService.isItemCachingEnabled() } returns false
|
every { appSettingsService.isItemCachingEnabled() } returns false
|
||||||
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
every { appSettingsService.isUpdateSourcesEnabled() } returns false
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
var testSources: List<SelfossModel.Source>
|
var testSources: List<SelfossModel.Source>
|
||||||
runBlocking {
|
runBlocking {
|
||||||
testSources = repository.getSourcesDetails()
|
testSources = repository.getSourcesDetails()
|
||||||
@@ -895,7 +897,7 @@ class RepositoryTest {
|
|||||||
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
|
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
|
||||||
SuccessResponse(true)
|
SuccessResponse(true)
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
var response: Boolean
|
var response: Boolean
|
||||||
runBlocking {
|
runBlocking {
|
||||||
response =
|
response =
|
||||||
@@ -952,7 +954,7 @@ class RepositoryTest {
|
|||||||
fun delete_source_without_connection() {
|
fun delete_source_without_connection() {
|
||||||
coEvery { api.deleteSource(any()) } returns SuccessResponse(false)
|
coEvery { api.deleteSource(any()) } returns SuccessResponse(false)
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
var response: Boolean
|
var response: Boolean
|
||||||
runBlocking {
|
runBlocking {
|
||||||
response = repository.deleteSource(5, "src")
|
response = repository.deleteSource(5, "src")
|
||||||
@@ -1025,7 +1027,7 @@ class RepositoryTest {
|
|||||||
data = "undocumented...",
|
data = "undocumented...",
|
||||||
)
|
)
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
var response: Boolean
|
var response: Boolean
|
||||||
runBlocking {
|
runBlocking {
|
||||||
response = repository.updateRemote()
|
response = repository.updateRemote()
|
||||||
@@ -1067,7 +1069,7 @@ class RepositoryTest {
|
|||||||
fun login_but_without_connection() {
|
fun login_but_without_connection() {
|
||||||
coEvery { api.login() } returns SuccessResponse(success = true)
|
coEvery { api.login() } returns SuccessResponse(success = true)
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
var response: Boolean
|
var response: Boolean
|
||||||
runBlocking {
|
runBlocking {
|
||||||
response = repository.login()
|
response = repository.login()
|
||||||
@@ -1147,7 +1149,7 @@ class RepositoryTest {
|
|||||||
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
|
||||||
StatusAndData(success = false, data = generateTestApiItem())
|
StatusAndData(success = false, data = generateTestApiItem())
|
||||||
|
|
||||||
initializeRepository(false)
|
initializeRepository(MutableStateFlow(false))
|
||||||
prepareSearch()
|
prepareSearch()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
repository.tryToCacheItemsAndGetNewOnes()
|
repository.tryToCacheItemsAndGetNewOnes()
|
||||||
|
@@ -44,7 +44,7 @@ class FakeItemParameters {
|
|||||||
var datetime = "2022-09-09T03:32:01-04:00"
|
var datetime = "2022-09-09T03:32:01-04:00"
|
||||||
val title = "Etica della ricerca sotto i riflettori."
|
val title = "Etica della ricerca sotto i riflettori."
|
||||||
val content =
|
val content =
|
||||||
"<p><strong>Luigi Campanella, già Presidente SCI</strong></p>\n<p>L’etica della scienza è di certo ambito di cui continuiamo a scoprire nuovi aspetti e risvolti.</p>\n<p>L’ultimo è quello delle intelligenze artificiali capaci di creare opere complesse basate su immagini e parole memorizzate con il rischio di fake news e di contenuti disturbanti.</p>\n<p>Per evitare che ciò accada si sta procedendo filtrando secondo criteri di autocensura i dati da cui l’intelligenza artificiale parte.</p>\n<p>Comincia ad intravedersi un futuro prossimo di competizione fra autori umani ed artificiali nel quale sarà importante, quando i loro prodotti saranno indistinguibili, dichiararne l’origine.</p>\n<p>Come si comprende, si conferma che gli aspetti etici dell’innovazione e della ricerca si diversificato sempre di più.</p>\n<p>La biologia molecolare e la genetica già in passato hanno posto all’attenzione comune aspetti di etica della scienza che hanno indotto a nuove riflessioni circa i limiti delle ricerche.</p>\n<p>L’argomento, sempre attuale, torna sulle prime pagine a seguito della pubblicazione di una ricerca della Università di Cambridge che ha sviluppato una struttura cellulare di un topo con un cuore che batte regolarmente.</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image002-1.png?w=481\" alt=\"\" width=\"697\" height=\"430\" /><img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image003-1.png?w=906\" alt=\"\" /><p>Magdalena Zernicka-Goetz</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image004.jpg?w=474\" alt=\"\" width=\"622\" height=\"465\" /><p>Gianluca Amadei</p>\n<p>Del gruppo fa parte anche uno scienziato italiano Gianluca Amadei,che dinnanzi alle obiezioni di natura etica sulla realizzazione della vita artificiale si è affrettato a sostenere che non è creare nuove vite il fine primario della ricerca, ma quello di salvare quelle esistenti, di dare contributi essenziali alla medicina citando il caso del fallimento tuttora non interpretato di alcune gravidanze e di superare la sperimentazione animale, così contribuendo positivamente alla soluzione di un altro dilemma etico.</p>\n<p>L’embrione sintetico ha ovviamente come primo traguardo il contributo ai trapianti oggi drammaticamente carenti nell’offerta rispetto alla domanda, con attese fino a 4 anni per i trapianti di cuore ed a 2 anni per quelli di fegato. Il lavoro dovrebbe adesso continuare presso l’Ateneo di Padova per creare nuovi organi e nuovi farmaci.</p>"
|
"<p><strong>Luigi Campanella, già Presidente SCI</strong></p>\n<p>L’etica della scienza è di certo ambito di cui continuiamo</p>"
|
||||||
var unread = true
|
var unread = true
|
||||||
var starred = true
|
var starred = true
|
||||||
val thumbnail = null
|
val thumbnail = null
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
// trick: for the same plugin versions in all sub-modules
|
//trick: for the same plugin versions in all sub-modules
|
||||||
id("com.android.application").version("8.8.1").apply(false)
|
id("com.android.application").version("8.7.3").apply(false)
|
||||||
id("com.android.library").version("8.8.1").apply(false)
|
id("com.android.library").version("8.7.3").apply(false)
|
||||||
id("org.jetbrains.kotlin.android").version("2.1.0").apply(false)
|
id("org.jetbrains.kotlin.android").version("2.1.0").apply(false)
|
||||||
kotlin("multiplatform").version("2.1.0").apply(false)
|
kotlin("multiplatform").version("2.1.0").apply(false)
|
||||||
id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
|
id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
|
||||||
@@ -16,6 +16,7 @@ allprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
tasks.register("clean", Delete::class) {
|
tasks.register("clean", Delete::class) {
|
||||||
delete(layout.buildDirectory)
|
delete(layout.buildDirectory)
|
||||||
}
|
}
|
||||||
@@ -23,4 +24,4 @@ tasks.register("clean", Delete::class) {
|
|||||||
dependencies {
|
dependencies {
|
||||||
kover(project(":shared"))
|
kover(project(":shared"))
|
||||||
kover(project(":androidApp"))
|
kover(project(":androidApp"))
|
||||||
}
|
}
|
786
detekt.yml
786
detekt.yml
@@ -1,786 +0,0 @@
|
|||||||
build:
|
|
||||||
maxIssues: 0
|
|
||||||
excludeCorrectable: false
|
|
||||||
weights:
|
|
||||||
# complexity: 2
|
|
||||||
# LongParameterList: 1
|
|
||||||
# style: 1
|
|
||||||
# comments: 1
|
|
||||||
|
|
||||||
config:
|
|
||||||
validation: true
|
|
||||||
warningsAsErrors: false
|
|
||||||
checkExhaustiveness: false
|
|
||||||
# when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
|
|
||||||
excludes: ''
|
|
||||||
|
|
||||||
processors:
|
|
||||||
active: true
|
|
||||||
exclude:
|
|
||||||
- 'DetektProgressListener'
|
|
||||||
# - 'KtFileCountProcessor'
|
|
||||||
# - 'PackageCountProcessor'
|
|
||||||
# - 'ClassCountProcessor'
|
|
||||||
# - 'FunctionCountProcessor'
|
|
||||||
# - 'PropertyCountProcessor'
|
|
||||||
# - 'ProjectComplexityProcessor'
|
|
||||||
# - 'ProjectCognitiveComplexityProcessor'
|
|
||||||
# - 'ProjectLLOCProcessor'
|
|
||||||
# - 'ProjectCLOCProcessor'
|
|
||||||
# - 'ProjectLOCProcessor'
|
|
||||||
# - 'ProjectSLOCProcessor'
|
|
||||||
# - 'LicenseHeaderLoaderExtension'
|
|
||||||
|
|
||||||
console-reports:
|
|
||||||
active: true
|
|
||||||
exclude:
|
|
||||||
- 'ProjectStatisticsReport'
|
|
||||||
- 'ComplexityReport'
|
|
||||||
- 'NotificationReport'
|
|
||||||
- 'FindingsReport'
|
|
||||||
- 'FileBasedFindingsReport'
|
|
||||||
# - 'LiteFindingsReport'
|
|
||||||
|
|
||||||
output-reports:
|
|
||||||
active: true
|
|
||||||
exclude:
|
|
||||||
# - 'TxtOutputReport'
|
|
||||||
# - 'XmlOutputReport'
|
|
||||||
# - 'HtmlOutputReport'
|
|
||||||
# - 'MdOutputReport'
|
|
||||||
# - 'SarifOutputReport'
|
|
||||||
|
|
||||||
comments:
|
|
||||||
active: true
|
|
||||||
AbsentOrWrongFileLicense:
|
|
||||||
active: false
|
|
||||||
licenseTemplateFile: 'license.template'
|
|
||||||
licenseTemplateIsRegex: false
|
|
||||||
CommentOverPrivateFunction:
|
|
||||||
active: false
|
|
||||||
CommentOverPrivateProperty:
|
|
||||||
active: false
|
|
||||||
DeprecatedBlockTag:
|
|
||||||
active: false
|
|
||||||
EndOfSentenceFormat:
|
|
||||||
active: false
|
|
||||||
endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
|
|
||||||
KDocReferencesNonPublicProperty:
|
|
||||||
active: false
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
|
|
||||||
OutdatedDocumentation:
|
|
||||||
active: false
|
|
||||||
matchTypeParameters: true
|
|
||||||
matchDeclarationsOrder: true
|
|
||||||
allowParamOnConstructorProperties: false
|
|
||||||
UndocumentedPublicClass:
|
|
||||||
active: false
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
|
|
||||||
searchInNestedClass: true
|
|
||||||
searchInInnerClass: true
|
|
||||||
searchInInnerObject: true
|
|
||||||
searchInInnerInterface: true
|
|
||||||
searchInProtectedClass: false
|
|
||||||
UndocumentedPublicFunction:
|
|
||||||
active: false
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
|
|
||||||
searchProtectedFunction: false
|
|
||||||
UndocumentedPublicProperty:
|
|
||||||
active: false
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
|
|
||||||
searchProtectedProperty: false
|
|
||||||
|
|
||||||
complexity:
|
|
||||||
active: true
|
|
||||||
CognitiveComplexMethod:
|
|
||||||
active: false
|
|
||||||
threshold: 15
|
|
||||||
ComplexCondition:
|
|
||||||
active: true
|
|
||||||
threshold: 4
|
|
||||||
ComplexInterface:
|
|
||||||
active: false
|
|
||||||
threshold: 10
|
|
||||||
includeStaticDeclarations: false
|
|
||||||
includePrivateDeclarations: false
|
|
||||||
ignoreOverloaded: false
|
|
||||||
CyclomaticComplexMethod:
|
|
||||||
active: true
|
|
||||||
threshold: 15
|
|
||||||
ignoreSingleWhenExpression: false
|
|
||||||
ignoreSimpleWhenEntries: false
|
|
||||||
ignoreNestingFunctions: false
|
|
||||||
nestingFunctions:
|
|
||||||
- 'also'
|
|
||||||
- 'apply'
|
|
||||||
- 'forEach'
|
|
||||||
- 'isNotNull'
|
|
||||||
- 'ifNull'
|
|
||||||
- 'let'
|
|
||||||
- 'run'
|
|
||||||
- 'use'
|
|
||||||
- 'with'
|
|
||||||
LabeledExpression:
|
|
||||||
active: false
|
|
||||||
ignoredLabels: [ ]
|
|
||||||
LargeClass:
|
|
||||||
active: true
|
|
||||||
threshold: 600
|
|
||||||
LongMethod:
|
|
||||||
active: true
|
|
||||||
threshold: 60
|
|
||||||
LongParameterList:
|
|
||||||
active: true
|
|
||||||
functionThreshold: 6
|
|
||||||
constructorThreshold: 7
|
|
||||||
ignoreDefaultParameters: false
|
|
||||||
ignoreDataClasses: true
|
|
||||||
ignoreAnnotatedParameter: [ ]
|
|
||||||
MethodOverloading:
|
|
||||||
active: false
|
|
||||||
threshold: 6
|
|
||||||
NamedArguments:
|
|
||||||
active: false
|
|
||||||
threshold: 3
|
|
||||||
ignoreArgumentsMatchingNames: false
|
|
||||||
NestedBlockDepth:
|
|
||||||
active: true
|
|
||||||
threshold: 4
|
|
||||||
NestedScopeFunctions:
|
|
||||||
active: false
|
|
||||||
threshold: 1
|
|
||||||
functions:
|
|
||||||
- 'kotlin.apply'
|
|
||||||
- 'kotlin.run'
|
|
||||||
- 'kotlin.with'
|
|
||||||
- 'kotlin.let'
|
|
||||||
- 'kotlin.also'
|
|
||||||
ReplaceSafeCallChainWithRun:
|
|
||||||
active: false
|
|
||||||
StringLiteralDuplication:
|
|
||||||
active: false
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
|
|
||||||
threshold: 3
|
|
||||||
ignoreAnnotation: true
|
|
||||||
excludeStringsWithLessThan5Characters: true
|
|
||||||
ignoreStringsRegex: '$^'
|
|
||||||
TooManyFunctions:
|
|
||||||
active: true
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/android/*Activity.kt', '**/fragments/*Fragment.kt' ]
|
|
||||||
thresholdInFiles: 11
|
|
||||||
thresholdInClasses: 11
|
|
||||||
thresholdInInterfaces: 11
|
|
||||||
thresholdInObjects: 11
|
|
||||||
thresholdInEnums: 11
|
|
||||||
ignoreDeprecated: false
|
|
||||||
ignorePrivate: false
|
|
||||||
ignoreOverridden: false
|
|
||||||
ignoreAnnotatedFunctions: [ ]
|
|
||||||
|
|
||||||
coroutines:
|
|
||||||
active: true
|
|
||||||
GlobalCoroutineUsage:
|
|
||||||
active: false
|
|
||||||
InjectDispatcher:
|
|
||||||
active: true
|
|
||||||
dispatcherNames:
|
|
||||||
- 'IO'
|
|
||||||
- 'Default'
|
|
||||||
- 'Unconfined'
|
|
||||||
RedundantSuspendModifier:
|
|
||||||
active: true
|
|
||||||
SleepInsteadOfDelay:
|
|
||||||
active: true
|
|
||||||
SuspendFunSwallowedCancellation:
|
|
||||||
active: false
|
|
||||||
SuspendFunWithCoroutineScopeReceiver:
|
|
||||||
active: false
|
|
||||||
SuspendFunWithFlowReturnType:
|
|
||||||
active: true
|
|
||||||
|
|
||||||
empty-blocks:
|
|
||||||
active: true
|
|
||||||
EmptyCatchBlock:
|
|
||||||
active: true
|
|
||||||
allowedExceptionNameRegex: '_|(ignore|expected).*'
|
|
||||||
EmptyClassBlock:
|
|
||||||
active: true
|
|
||||||
EmptyDefaultConstructor:
|
|
||||||
active: true
|
|
||||||
EmptyDoWhileBlock:
|
|
||||||
active: true
|
|
||||||
EmptyElseBlock:
|
|
||||||
active: true
|
|
||||||
EmptyFinallyBlock:
|
|
||||||
active: true
|
|
||||||
EmptyForBlock:
|
|
||||||
active: true
|
|
||||||
EmptyFunctionBlock:
|
|
||||||
active: true
|
|
||||||
ignoreOverridden: false
|
|
||||||
EmptyIfBlock:
|
|
||||||
active: true
|
|
||||||
EmptyInitBlock:
|
|
||||||
active: true
|
|
||||||
EmptyKtFile:
|
|
||||||
active: true
|
|
||||||
EmptySecondaryConstructor:
|
|
||||||
active: true
|
|
||||||
EmptyTryBlock:
|
|
||||||
active: true
|
|
||||||
EmptyWhenBlock:
|
|
||||||
active: true
|
|
||||||
EmptyWhileBlock:
|
|
||||||
active: true
|
|
||||||
|
|
||||||
exceptions:
|
|
||||||
active: true
|
|
||||||
ExceptionRaisedInUnexpectedLocation:
|
|
||||||
active: true
|
|
||||||
methodNames:
|
|
||||||
- 'equals'
|
|
||||||
- 'finalize'
|
|
||||||
- 'hashCode'
|
|
||||||
- 'toString'
|
|
||||||
InstanceOfCheckForException:
|
|
||||||
active: true
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
|
|
||||||
NotImplementedDeclaration:
|
|
||||||
active: false
|
|
||||||
ObjectExtendsThrowable:
|
|
||||||
active: false
|
|
||||||
PrintStackTrace:
|
|
||||||
active: true
|
|
||||||
RethrowCaughtException:
|
|
||||||
active: true
|
|
||||||
ReturnFromFinally:
|
|
||||||
active: true
|
|
||||||
ignoreLabeled: false
|
|
||||||
SwallowedException:
|
|
||||||
active: true
|
|
||||||
ignoredExceptionTypes:
|
|
||||||
- 'InterruptedException'
|
|
||||||
- 'MalformedURLException'
|
|
||||||
- 'NumberFormatException'
|
|
||||||
- 'ParseException'
|
|
||||||
allowedExceptionNameRegex: '_|(ignore|expected).*'
|
|
||||||
ThrowingExceptionFromFinally:
|
|
||||||
active: true
|
|
||||||
ThrowingExceptionInMain:
|
|
||||||
active: false
|
|
||||||
ThrowingExceptionsWithoutMessageOrCause:
|
|
||||||
active: true
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
|
|
||||||
exceptions:
|
|
||||||
- 'ArrayIndexOutOfBoundsException'
|
|
||||||
- 'Exception'
|
|
||||||
- 'IllegalArgumentException'
|
|
||||||
- 'IllegalMonitorStateException'
|
|
||||||
- 'IllegalStateException'
|
|
||||||
- 'IndexOutOfBoundsException'
|
|
||||||
- 'NullPointerException'
|
|
||||||
- 'RuntimeException'
|
|
||||||
- 'Throwable'
|
|
||||||
ThrowingNewInstanceOfSameException:
|
|
||||||
active: true
|
|
||||||
TooGenericExceptionCaught:
|
|
||||||
active: false
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
|
|
||||||
exceptionNames:
|
|
||||||
- 'ArrayIndexOutOfBoundsException'
|
|
||||||
- 'Error'
|
|
||||||
- 'Exception'
|
|
||||||
- 'IllegalMonitorStateException'
|
|
||||||
- 'IndexOutOfBoundsException'
|
|
||||||
- 'NullPointerException'
|
|
||||||
- 'RuntimeException'
|
|
||||||
- 'Throwable'
|
|
||||||
allowedExceptionNameRegex: '_|(ignore|expected).*'
|
|
||||||
TooGenericExceptionThrown:
|
|
||||||
active: true
|
|
||||||
exceptionNames:
|
|
||||||
- 'Error'
|
|
||||||
- 'Exception'
|
|
||||||
- 'RuntimeException'
|
|
||||||
- 'Throwable'
|
|
||||||
|
|
||||||
naming:
|
|
||||||
active: true
|
|
||||||
BooleanPropertyNaming:
|
|
||||||
active: false
|
|
||||||
allowedPattern: '^(is|has|are)'
|
|
||||||
ClassNaming:
|
|
||||||
active: true
|
|
||||||
classPattern: '[A-Z][a-zA-Z0-9]*'
|
|
||||||
ConstructorParameterNaming:
|
|
||||||
active: true
|
|
||||||
parameterPattern: '[a-z][A-Za-z0-9]*'
|
|
||||||
privateParameterPattern: '[a-z][A-Za-z0-9]*'
|
|
||||||
excludeClassPattern: '$^'
|
|
||||||
EnumNaming:
|
|
||||||
active: true
|
|
||||||
enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
|
|
||||||
ForbiddenClassName:
|
|
||||||
active: false
|
|
||||||
forbiddenName: [ ]
|
|
||||||
FunctionMaxLength:
|
|
||||||
active: false
|
|
||||||
maximumFunctionNameLength: 30
|
|
||||||
FunctionMinLength:
|
|
||||||
active: false
|
|
||||||
minimumFunctionNameLength: 3
|
|
||||||
FunctionNaming:
|
|
||||||
active: true
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
|
|
||||||
functionPattern: '[a-z][a-zA-Z0-9]*'
|
|
||||||
excludeClassPattern: '$^'
|
|
||||||
FunctionParameterNaming:
|
|
||||||
active: true
|
|
||||||
parameterPattern: '[a-z][A-Za-z0-9]*'
|
|
||||||
excludeClassPattern: '$^'
|
|
||||||
InvalidPackageDeclaration:
|
|
||||||
active: true
|
|
||||||
rootPackage: ''
|
|
||||||
requireRootInDeclaration: false
|
|
||||||
LambdaParameterNaming:
|
|
||||||
active: false
|
|
||||||
parameterPattern: '[a-z][A-Za-z0-9]*|_'
|
|
||||||
MatchingDeclarationName:
|
|
||||||
active: false # done in ktlint
|
|
||||||
mustBeFirst: true
|
|
||||||
MemberNameEqualsClassName:
|
|
||||||
active: true
|
|
||||||
ignoreOverridden: true
|
|
||||||
NoNameShadowing:
|
|
||||||
active: true
|
|
||||||
NonBooleanPropertyPrefixedWithIs:
|
|
||||||
active: false
|
|
||||||
ObjectPropertyNaming:
|
|
||||||
active: true
|
|
||||||
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
|
|
||||||
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
|
|
||||||
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
|
|
||||||
PackageNaming:
|
|
||||||
active: true
|
|
||||||
packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
|
|
||||||
TopLevelPropertyNaming:
|
|
||||||
active: true
|
|
||||||
constantPattern: '[A-Z][_A-Z0-9]*'
|
|
||||||
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
|
|
||||||
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
|
|
||||||
VariableMaxLength:
|
|
||||||
active: false
|
|
||||||
maximumVariableNameLength: 64
|
|
||||||
VariableMinLength:
|
|
||||||
active: false
|
|
||||||
minimumVariableNameLength: 1
|
|
||||||
VariableNaming:
|
|
||||||
active: true
|
|
||||||
variablePattern: '[a-z][A-Za-z0-9]*'
|
|
||||||
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
|
|
||||||
excludeClassPattern: '$^'
|
|
||||||
|
|
||||||
performance:
|
|
||||||
active: true
|
|
||||||
ArrayPrimitive:
|
|
||||||
active: true
|
|
||||||
CouldBeSequence:
|
|
||||||
active: false
|
|
||||||
threshold: 3
|
|
||||||
ForEachOnRange:
|
|
||||||
active: true
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
|
|
||||||
SpreadOperator:
|
|
||||||
active: true
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
|
|
||||||
UnnecessaryPartOfBinaryExpression:
|
|
||||||
active: false
|
|
||||||
UnnecessaryTemporaryInstantiation:
|
|
||||||
active: true
|
|
||||||
|
|
||||||
potential-bugs:
|
|
||||||
active: true
|
|
||||||
AvoidReferentialEquality:
|
|
||||||
active: true
|
|
||||||
forbiddenTypePatterns:
|
|
||||||
- 'kotlin.String'
|
|
||||||
CastNullableToNonNullableType:
|
|
||||||
active: false
|
|
||||||
CastToNullableType:
|
|
||||||
active: false
|
|
||||||
Deprecation:
|
|
||||||
active: false
|
|
||||||
DontDowncastCollectionTypes:
|
|
||||||
active: false
|
|
||||||
DoubleMutabilityForCollection:
|
|
||||||
active: true
|
|
||||||
mutableTypes:
|
|
||||||
- 'kotlin.collections.MutableList'
|
|
||||||
- 'kotlin.collections.MutableMap'
|
|
||||||
- 'kotlin.collections.MutableSet'
|
|
||||||
- 'java.util.ArrayList'
|
|
||||||
- 'java.util.LinkedHashSet'
|
|
||||||
- 'java.util.HashSet'
|
|
||||||
- 'java.util.LinkedHashMap'
|
|
||||||
- 'java.util.HashMap'
|
|
||||||
ElseCaseInsteadOfExhaustiveWhen:
|
|
||||||
active: false
|
|
||||||
ignoredSubjectTypes: [ ]
|
|
||||||
EqualsAlwaysReturnsTrueOrFalse:
|
|
||||||
active: true
|
|
||||||
EqualsWithHashCodeExist:
|
|
||||||
active: true
|
|
||||||
ExitOutsideMain:
|
|
||||||
active: false
|
|
||||||
ExplicitGarbageCollectionCall:
|
|
||||||
active: true
|
|
||||||
HasPlatformType:
|
|
||||||
active: true
|
|
||||||
IgnoredReturnValue:
|
|
||||||
active: true
|
|
||||||
restrictToConfig: true
|
|
||||||
returnValueAnnotations:
|
|
||||||
- 'CheckResult'
|
|
||||||
- '*.CheckResult'
|
|
||||||
- 'CheckReturnValue'
|
|
||||||
- '*.CheckReturnValue'
|
|
||||||
ignoreReturnValueAnnotations:
|
|
||||||
- 'CanIgnoreReturnValue'
|
|
||||||
- '*.CanIgnoreReturnValue'
|
|
||||||
returnValueTypes:
|
|
||||||
- 'kotlin.sequences.Sequence'
|
|
||||||
- 'kotlinx.coroutines.flow.*Flow'
|
|
||||||
- 'java.util.stream.*Stream'
|
|
||||||
ignoreFunctionCall: [ ]
|
|
||||||
ImplicitDefaultLocale:
|
|
||||||
active: true
|
|
||||||
ImplicitUnitReturnType:
|
|
||||||
active: false
|
|
||||||
allowExplicitReturnType: true
|
|
||||||
InvalidRange:
|
|
||||||
active: true
|
|
||||||
IteratorHasNextCallsNextMethod:
|
|
||||||
active: true
|
|
||||||
IteratorNotThrowingNoSuchElementException:
|
|
||||||
active: true
|
|
||||||
LateinitUsage:
|
|
||||||
active: false
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
|
|
||||||
ignoreOnClassesPattern: ''
|
|
||||||
MapGetWithNotNullAssertionOperator:
|
|
||||||
active: true
|
|
||||||
MissingPackageDeclaration:
|
|
||||||
active: false
|
|
||||||
excludes: [ '**/*.kts' ]
|
|
||||||
NullCheckOnMutableProperty:
|
|
||||||
active: false
|
|
||||||
NullableToStringCall:
|
|
||||||
active: false
|
|
||||||
PropertyUsedBeforeDeclaration:
|
|
||||||
active: false
|
|
||||||
UnconditionalJumpStatementInLoop:
|
|
||||||
active: false
|
|
||||||
UnnecessaryNotNullCheck:
|
|
||||||
active: false
|
|
||||||
UnnecessaryNotNullOperator:
|
|
||||||
active: true
|
|
||||||
UnnecessarySafeCall:
|
|
||||||
active: true
|
|
||||||
UnreachableCatchBlock:
|
|
||||||
active: true
|
|
||||||
UnreachableCode:
|
|
||||||
active: true
|
|
||||||
UnsafeCallOnNullableType:
|
|
||||||
active: true
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**' ]
|
|
||||||
UnsafeCast:
|
|
||||||
active: true
|
|
||||||
UnusedUnaryOperator:
|
|
||||||
active: true
|
|
||||||
UselessPostfixExpression:
|
|
||||||
active: true
|
|
||||||
WrongEqualsTypeParameter:
|
|
||||||
active: true
|
|
||||||
|
|
||||||
style:
|
|
||||||
active: true
|
|
||||||
AlsoCouldBeApply:
|
|
||||||
active: false
|
|
||||||
BracesOnIfStatements:
|
|
||||||
active: false
|
|
||||||
singleLine: 'never'
|
|
||||||
multiLine: 'always'
|
|
||||||
BracesOnWhenStatements:
|
|
||||||
active: false
|
|
||||||
singleLine: 'necessary'
|
|
||||||
multiLine: 'consistent'
|
|
||||||
CanBeNonNullable:
|
|
||||||
active: false
|
|
||||||
CascadingCallWrapping:
|
|
||||||
active: false
|
|
||||||
includeElvis: true
|
|
||||||
ClassOrdering:
|
|
||||||
active: false
|
|
||||||
CollapsibleIfStatements:
|
|
||||||
active: false
|
|
||||||
DataClassContainsFunctions:
|
|
||||||
active: false
|
|
||||||
conversionFunctionPrefix:
|
|
||||||
- 'to'
|
|
||||||
allowOperators: false
|
|
||||||
DataClassShouldBeImmutable:
|
|
||||||
active: false
|
|
||||||
DestructuringDeclarationWithTooManyEntries:
|
|
||||||
active: true
|
|
||||||
maxDestructuringEntries: 3
|
|
||||||
DoubleNegativeLambda:
|
|
||||||
active: false
|
|
||||||
negativeFunctions:
|
|
||||||
- reason: 'Use `takeIf` instead.'
|
|
||||||
value: 'takeUnless'
|
|
||||||
- reason: 'Use `all` instead.'
|
|
||||||
value: 'none'
|
|
||||||
negativeFunctionNameParts:
|
|
||||||
- 'not'
|
|
||||||
- 'non'
|
|
||||||
EqualsNullCall:
|
|
||||||
active: true
|
|
||||||
EqualsOnSignatureLine:
|
|
||||||
active: false
|
|
||||||
ExplicitCollectionElementAccessMethod:
|
|
||||||
active: false
|
|
||||||
ExplicitItLambdaParameter:
|
|
||||||
active: true
|
|
||||||
ExpressionBodySyntax:
|
|
||||||
active: false
|
|
||||||
includeLineWrapping: false
|
|
||||||
ForbiddenAnnotation:
|
|
||||||
active: false
|
|
||||||
annotations:
|
|
||||||
- reason: 'it is a java annotation. Use `Suppress` instead.'
|
|
||||||
value: 'java.lang.SuppressWarnings'
|
|
||||||
- reason: 'it is a java annotation. Use `kotlin.Deprecated` instead.'
|
|
||||||
value: 'java.lang.Deprecated'
|
|
||||||
- reason: 'it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead.'
|
|
||||||
value: 'java.lang.annotation.Documented'
|
|
||||||
- reason: 'it is a java annotation. Use `kotlin.annotation.Target` instead.'
|
|
||||||
value: 'java.lang.annotation.Target'
|
|
||||||
- reason: 'it is a java annotation. Use `kotlin.annotation.Retention` instead.'
|
|
||||||
value: 'java.lang.annotation.Retention'
|
|
||||||
- reason: 'it is a java annotation. Use `kotlin.annotation.Repeatable` instead.'
|
|
||||||
value: 'java.lang.annotation.Repeatable'
|
|
||||||
- reason: 'Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265'
|
|
||||||
value: 'java.lang.annotation.Inherited'
|
|
||||||
ForbiddenComment:
|
|
||||||
active: true
|
|
||||||
comments:
|
|
||||||
- reason: 'Forbidden FIXME todo marker in comment, please fix the problem.'
|
|
||||||
value: 'FIXME:'
|
|
||||||
- reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.'
|
|
||||||
value: 'STOPSHIP:'
|
|
||||||
- reason: 'Forbidden TODO todo marker in comment, please do the changes.'
|
|
||||||
value: 'TODO:'
|
|
||||||
allowedPatterns: ''
|
|
||||||
ForbiddenImport:
|
|
||||||
active: false
|
|
||||||
imports: [ ]
|
|
||||||
forbiddenPatterns: ''
|
|
||||||
ForbiddenMethodCall:
|
|
||||||
active: false
|
|
||||||
methods:
|
|
||||||
- reason: 'print does not allow you to configure the output stream. Use a logger instead.'
|
|
||||||
value: 'kotlin.io.print'
|
|
||||||
- reason: 'println does not allow you to configure the output stream. Use a logger instead.'
|
|
||||||
value: 'kotlin.io.println'
|
|
||||||
ForbiddenSuppress:
|
|
||||||
active: false
|
|
||||||
rules: [ ]
|
|
||||||
ForbiddenVoid:
|
|
||||||
active: true
|
|
||||||
ignoreOverridden: false
|
|
||||||
ignoreUsageInGenerics: false
|
|
||||||
FunctionOnlyReturningConstant:
|
|
||||||
active: true
|
|
||||||
ignoreOverridableFunction: true
|
|
||||||
ignoreActualFunction: true
|
|
||||||
excludedFunctions: [ ]
|
|
||||||
LoopWithTooManyJumpStatements:
|
|
||||||
active: true
|
|
||||||
maxJumpCount: 1
|
|
||||||
MagicNumber:
|
|
||||||
active: true
|
|
||||||
excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/androidUnitTest/**', '**/androidInstrumentedTest/**', '**/jsTest/**', '**/iosTest/**', '**/*.kts' ]
|
|
||||||
ignoreNumbers:
|
|
||||||
- '-1'
|
|
||||||
- '0'
|
|
||||||
- '1'
|
|
||||||
- '2'
|
|
||||||
ignoreHashCodeFunction: true
|
|
||||||
ignorePropertyDeclaration: false
|
|
||||||
ignoreLocalVariableDeclaration: false
|
|
||||||
ignoreConstantDeclaration: true
|
|
||||||
ignoreCompanionObjectPropertyDeclaration: true
|
|
||||||
ignoreAnnotation: false
|
|
||||||
ignoreNamedArgument: true
|
|
||||||
ignoreEnums: false
|
|
||||||
ignoreRanges: false
|
|
||||||
ignoreExtensionFunctions: true
|
|
||||||
MandatoryBracesLoops:
|
|
||||||
active: false
|
|
||||||
MaxChainedCallsOnSameLine:
|
|
||||||
active: false
|
|
||||||
maxChainedCalls: 5
|
|
||||||
MaxLineLength:
|
|
||||||
active: false # done in ktlint
|
|
||||||
maxLineLength: 140 # default is 120. 140 to match ktlint
|
|
||||||
excludePackageStatements: true
|
|
||||||
excludeImportStatements: true
|
|
||||||
excludeCommentStatements: false
|
|
||||||
excludeRawStrings: true
|
|
||||||
MayBeConst:
|
|
||||||
active: true
|
|
||||||
ModifierOrder:
|
|
||||||
active: true
|
|
||||||
MultilineLambdaItParameter:
|
|
||||||
active: false
|
|
||||||
MultilineRawStringIndentation:
|
|
||||||
active: false
|
|
||||||
indentSize: 4
|
|
||||||
trimmingMethods:
|
|
||||||
- 'trimIndent'
|
|
||||||
- 'trimMargin'
|
|
||||||
NestedClassesVisibility:
|
|
||||||
active: true
|
|
||||||
NewLineAtEndOfFile:
|
|
||||||
active: false # done in ktlint
|
|
||||||
NoTabs:
|
|
||||||
active: false
|
|
||||||
NullableBooleanCheck:
|
|
||||||
active: false
|
|
||||||
ObjectLiteralToLambda:
|
|
||||||
active: true
|
|
||||||
OptionalAbstractKeyword:
|
|
||||||
active: true
|
|
||||||
OptionalUnit:
|
|
||||||
active: false
|
|
||||||
PreferToOverPairSyntax:
|
|
||||||
active: false
|
|
||||||
ProtectedMemberInFinalClass:
|
|
||||||
active: true
|
|
||||||
RedundantExplicitType:
|
|
||||||
active: false
|
|
||||||
RedundantHigherOrderMapUsage:
|
|
||||||
active: true
|
|
||||||
RedundantVisibilityModifierRule:
|
|
||||||
active: false
|
|
||||||
ReturnCount:
|
|
||||||
active: true
|
|
||||||
max: 2
|
|
||||||
excludedFunctions:
|
|
||||||
- 'equals'
|
|
||||||
excludeLabeled: false
|
|
||||||
excludeReturnFromLambda: true
|
|
||||||
excludeGuardClauses: false
|
|
||||||
SafeCast:
|
|
||||||
active: true
|
|
||||||
SerialVersionUIDInSerializableClass:
|
|
||||||
active: true
|
|
||||||
SpacingBetweenPackageAndImports:
|
|
||||||
active: false
|
|
||||||
StringShouldBeRawString:
|
|
||||||
active: false
|
|
||||||
maxEscapedCharacterCount: 2
|
|
||||||
ignoredCharacters: [ ]
|
|
||||||
ThrowsCount:
|
|
||||||
active: true
|
|
||||||
max: 2
|
|
||||||
excludeGuardClauses: false
|
|
||||||
TrailingWhitespace:
|
|
||||||
active: false
|
|
||||||
TrimMultilineRawString:
|
|
||||||
active: false
|
|
||||||
trimmingMethods:
|
|
||||||
- 'trimIndent'
|
|
||||||
- 'trimMargin'
|
|
||||||
UnderscoresInNumericLiterals:
|
|
||||||
active: false
|
|
||||||
acceptableLength: 4
|
|
||||||
allowNonStandardGrouping: false
|
|
||||||
UnnecessaryAbstractClass:
|
|
||||||
active: true
|
|
||||||
UnnecessaryAnnotationUseSiteTarget:
|
|
||||||
active: false
|
|
||||||
UnnecessaryApply:
|
|
||||||
active: true
|
|
||||||
UnnecessaryBackticks:
|
|
||||||
active: false
|
|
||||||
UnnecessaryBracesAroundTrailingLambda:
|
|
||||||
active: false
|
|
||||||
UnnecessaryFilter:
|
|
||||||
active: true
|
|
||||||
UnnecessaryInheritance:
|
|
||||||
active: true
|
|
||||||
UnnecessaryInnerClass:
|
|
||||||
active: false
|
|
||||||
UnnecessaryLet:
|
|
||||||
active: false
|
|
||||||
UnnecessaryParentheses:
|
|
||||||
active: false
|
|
||||||
allowForUnclearPrecedence: false
|
|
||||||
UntilInsteadOfRangeTo:
|
|
||||||
active: false
|
|
||||||
UnusedImports:
|
|
||||||
active: false
|
|
||||||
UnusedParameter:
|
|
||||||
active: true
|
|
||||||
allowedNames: 'ignored|expected'
|
|
||||||
UnusedPrivateClass:
|
|
||||||
active: true
|
|
||||||
UnusedPrivateMember:
|
|
||||||
active: true
|
|
||||||
allowedNames: ''
|
|
||||||
UnusedPrivateProperty:
|
|
||||||
active: true
|
|
||||||
allowedNames: '_|ignored|expected|serialVersionUID'
|
|
||||||
excludes: [ '**/build.gradle.kts' ]
|
|
||||||
UseAnyOrNoneInsteadOfFind:
|
|
||||||
active: true
|
|
||||||
UseArrayLiteralsInAnnotations:
|
|
||||||
active: true
|
|
||||||
UseCheckNotNull:
|
|
||||||
active: true
|
|
||||||
UseCheckOrError:
|
|
||||||
active: true
|
|
||||||
UseDataClass:
|
|
||||||
active: false
|
|
||||||
allowVars: false
|
|
||||||
UseEmptyCounterpart:
|
|
||||||
active: false
|
|
||||||
UseIfEmptyOrIfBlank:
|
|
||||||
active: false
|
|
||||||
UseIfInsteadOfWhen:
|
|
||||||
active: false
|
|
||||||
ignoreWhenContainingVariableDeclaration: false
|
|
||||||
UseIsNullOrEmpty:
|
|
||||||
active: true
|
|
||||||
UseLet:
|
|
||||||
active: false
|
|
||||||
UseOrEmpty:
|
|
||||||
active: true
|
|
||||||
UseRequire:
|
|
||||||
active: true
|
|
||||||
UseRequireNotNull:
|
|
||||||
active: true
|
|
||||||
UseSumOfInsteadOfFlatMapSize:
|
|
||||||
active: false
|
|
||||||
UselessCallOnNotNull:
|
|
||||||
active: true
|
|
||||||
UtilityClassWithPublicConstructor:
|
|
||||||
active: true
|
|
||||||
VarCouldBeVal:
|
|
||||||
active: true
|
|
||||||
ignoreLateinitVar: false
|
|
||||||
WildcardImport:
|
|
||||||
active: true
|
|
||||||
excludeImports:
|
|
||||||
- 'java.util.*'
|
|
@@ -1,4 +0,0 @@
|
|||||||
**v125010111**
|
|
||||||
|
|
||||||
- Debug trying to fix context issues. (#174)
|
|
||||||
- Changelog for v125010031
|
|
@@ -1,5 +0,0 @@
|
|||||||
**v125010131**
|
|
||||||
|
|
||||||
- fix: reload the adapter when it's needed. Fixes #128. (#176)
|
|
||||||
- feat: basic auth and images loading. Fixes #172. (#175)
|
|
||||||
- Changelog for v125010111
|
|
@@ -1,6 +0,0 @@
|
|||||||
**v125010201**
|
|
||||||
|
|
||||||
- fix: Handle empty url issue.
|
|
||||||
- Merge pull request 'Removed the floating bar.' (#177) from floating-bar into master
|
|
||||||
- chore: changing actions in reader fragment.
|
|
||||||
- Changelog for v125010131
|
|
@@ -1,8 +0,0 @@
|
|||||||
**v125010241**
|
|
||||||
|
|
||||||
- Merge pull request 'fix: Link not opening.' (#178) from fix-open-link into master
|
|
||||||
- refactor: context fragments issues.
|
|
||||||
- logs: Context issues.
|
|
||||||
- fix: Handle empty url issue, again.
|
|
||||||
- fix: Link not opening.
|
|
||||||
- Changelog for v125010201
|
|
@@ -1,7 +0,0 @@
|
|||||||
**v125020411**
|
|
||||||
|
|
||||||
- Merge pull request 'bump' (#182) from bump into master
|
|
||||||
- chore: non transiant R classes.
|
|
||||||
- Merge pull request 'fix: One more missing context.' (#181) from fix-one-more-context into master
|
|
||||||
- bump
|
|
||||||
- fix: One more missing context.
|
|
@@ -1,7 +0,0 @@
|
|||||||
**v125020471**
|
|
||||||
|
|
||||||
- chore: no more docker-compose.
|
|
||||||
- bump: gradle plugin.
|
|
||||||
- Merge pull request 'fix: check index exists.' (#183) from fix-index into master
|
|
||||||
- fix: check index exists.
|
|
||||||
- Changelog for v125020411
|
|
@@ -1,4 +0,0 @@
|
|||||||
**v125020581**
|
|
||||||
|
|
||||||
- fix: url can be empty ?
|
|
||||||
- Changelog for v125020471
|
|
@@ -1,12 +0,0 @@
|
|||||||
**v125030681**
|
|
||||||
|
|
||||||
- chore: do not send reports on simulators.
|
|
||||||
- Merge pull request 'chore: do not send reports on simulators.' (#188) from chore-acra-simulator into master
|
|
||||||
- chore: do not send reports on simulators.
|
|
||||||
- Merge pull request 'fix: Url validation was not failing login. Added tests.' (#186) from fix-invalid-url into master
|
|
||||||
- Merge pull request 'chore: crowding ci integration.' (#187) from chore-crowdin-ci into master
|
|
||||||
- chore: we don't need to check if the url is valid in upsert screen.
|
|
||||||
- fix: Url validation was not failing login. Added tests.
|
|
||||||
- chore: crowding ci integration.
|
|
||||||
- Show a confirmation dialog before deleting sources (#185)
|
|
||||||
- Changelog for v125020581
|
|
@@ -18,12 +18,12 @@ kotlin.code.style=official
|
|||||||
#Android
|
#Android
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
#android.nonTransitiveRClass=true
|
#android.nonTransitiveRClass=true
|
||||||
android.enableJetifier=false
|
android.enableJetifier=true
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=false
|
||||||
#MPP
|
#MPP
|
||||||
kotlin.mpp.enableCInteropCommonization=true
|
kotlin.mpp.enableCInteropCommonization=true
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
ignoreGitVersion=false
|
ignoreGitVersion=false
|
||||||
kotlin.native.cacheKind.iosX64=none
|
kotlin.native.cacheKind.iosX64=none
|
||||||
org.gradle.configureondemand=true
|
org.gradle.configureondemand=true
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
#Sun Feb 09 14:44:52 CET 2025
|
#Mon Nov 25 22:48:24 CET 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@@ -4,6 +4,7 @@ object SqlDelight {
|
|||||||
const val runtime = "app.cash.sqldelight:runtime:2.0.2"
|
const val runtime = "app.cash.sqldelight:runtime:2.0.2"
|
||||||
const val android = "app.cash.sqldelight:android-driver:2.0.2"
|
const val android = "app.cash.sqldelight:android-driver:2.0.2"
|
||||||
const val native = "app.cash.sqldelight:native-driver:2.0.2"
|
const val native = "app.cash.sqldelight:native-driver:2.0.2"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
@@ -40,13 +41,13 @@ kotlin {
|
|||||||
|
|
||||||
implementation("org.jsoup:jsoup:1.15.4")
|
implementation("org.jsoup:jsoup:1.15.4")
|
||||||
|
|
||||||
// Dependency Injection
|
//Dependency Injection
|
||||||
implementation("org.kodein.di:kodein-di:7.14.0")
|
implementation("org.kodein.di:kodein-di:7.14.0")
|
||||||
|
|
||||||
// Settings
|
//Settings
|
||||||
implementation("com.russhwolf:multiplatform-settings-no-arg:1.0.0-RC")
|
implementation("com.russhwolf:multiplatform-settings-no-arg:1.0.0-RC")
|
||||||
|
|
||||||
// Logging
|
//Logging
|
||||||
implementation("io.github.aakira:napier:2.6.1")
|
implementation("io.github.aakira:napier:2.6.1")
|
||||||
|
|
||||||
// Sql
|
// Sql
|
||||||
@@ -54,10 +55,6 @@ kotlin {
|
|||||||
|
|
||||||
// Sql
|
// Sql
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
|
||||||
|
|
||||||
// Connectivity
|
|
||||||
implementation("dev.jordond.connectivity:connectivity-core:1.2.0")
|
|
||||||
implementation("dev.jordond.connectivity:connectivity-device:1.2.0")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
@@ -117,4 +114,4 @@ sqldelight {
|
|||||||
packageName.set("bou.amine.apps.readerforselfossv2.dao")
|
packageName.set("bou.amine.apps.readerforselfossv2.dao")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -4,13 +4,12 @@ import android.content.Context
|
|||||||
import app.cash.sqldelight.db.SqlDriver
|
import app.cash.sqldelight.db.SqlDriver
|
||||||
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
||||||
|
|
||||||
actual class DriverFactory(
|
actual class DriverFactory(private val context: Context) {
|
||||||
private val context: Context,
|
actual fun createDriver(): SqlDriver {
|
||||||
) {
|
return AndroidSqliteDriver(
|
||||||
actual fun createDriver(): SqlDriver =
|
|
||||||
AndroidSqliteDriver(
|
|
||||||
ReaderForSelfossDB.Schema,
|
ReaderForSelfossDB.Schema,
|
||||||
context,
|
context,
|
||||||
"ReaderForSelfossV2-android.db",
|
"ReaderForSelfossV2-android.db",
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,14 +9,12 @@ class NaiveTrustManager : X509TrustManager {
|
|||||||
chain: Array<out X509Certificate>?,
|
chain: Array<out X509Certificate>?,
|
||||||
authType: String?,
|
authType: String?,
|
||||||
) {
|
) {
|
||||||
// Nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkServerTrusted(
|
override fun checkServerTrusted(
|
||||||
chain: Array<out X509Certificate>?,
|
chain: Array<out X509Certificate>?,
|
||||||
authType: String?,
|
authType: String?,
|
||||||
) {
|
) {
|
||||||
// Nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
|
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
|
||||||
|
@@ -3,7 +3,6 @@ package bou.amine.apps.readerforselfossv2.utils
|
|||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
|
|
||||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
|
||||||
actual class DateUtils {
|
actual class DateUtils {
|
||||||
actual companion object {
|
actual companion object {
|
||||||
actual fun parseRelativeDate(dateString: String): String {
|
actual fun parseRelativeDate(dateString: String): String {
|
||||||
|
@@ -12,14 +12,16 @@ actual fun SelfossModel.Item.getIcon(baseUrl: String): String = constructUrl(bas
|
|||||||
|
|
||||||
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String = constructUrl(baseUrl, "thumbnails", thumbnail)
|
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String = constructUrl(baseUrl, "thumbnails", thumbnail)
|
||||||
|
|
||||||
val IMAGE_EXTENSION_REGEXP = """\.(jpg|jpeg|png|webp)""".toRegex()
|
|
||||||
|
|
||||||
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
||||||
val allImages = ArrayList<String>()
|
val allImages = ArrayList<String>()
|
||||||
|
|
||||||
for (image in Jsoup.parse(content).getElementsByTag("img")) {
|
for (image in Jsoup.parse(content).getElementsByTag("img")) {
|
||||||
val url = image.attr("src")
|
val url = image.attr("src")
|
||||||
if (IMAGE_EXTENSION_REGEXP.containsMatchIn(url.lowercase(Locale.US))) {
|
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)
|
allImages.add(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,8 @@
|
|||||||
@file:Suppress("detekt:LongParameterList")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.model
|
package bou.amine.apps.readerforselfossv2.model
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
class MercuryModel {
|
class MercuryModel {
|
||||||
@Suppress("detekt:ConstructorParameterNaming")
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class ParsedContent(
|
class ParsedContent(
|
||||||
val title: String? = null,
|
val title: String? = null,
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
@file:Suppress("detekt:LongParameterList")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.model
|
package bou.amine.apps.readerforselfossv2.model
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||||
@@ -20,10 +18,6 @@ import kotlinx.serialization.json.booleanOrNull
|
|||||||
import kotlinx.serialization.json.int
|
import kotlinx.serialization.json.int
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
|
||||||
class ModelException(
|
|
||||||
message: String,
|
|
||||||
) : Throwable(message)
|
|
||||||
|
|
||||||
class SelfossModel {
|
class SelfossModel {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Tag(
|
data class Tag(
|
||||||
@@ -127,8 +121,8 @@ class SelfossModel {
|
|||||||
val tags: List<String>,
|
val tags: List<String>,
|
||||||
val author: String? = null,
|
val author: String? = null,
|
||||||
) {
|
) {
|
||||||
fun getLinkDecoded(): String? {
|
fun getLinkDecoded(): String {
|
||||||
var stringUrl: String?
|
var stringUrl: String
|
||||||
stringUrl =
|
stringUrl =
|
||||||
if (link.contains("//news.google.com/news/") && link.contains("&url=")) {
|
if (link.contains("//news.google.com/news/") && link.contains("&url=")) {
|
||||||
link.substringAfter("&url=")
|
link.substringAfter("&url=")
|
||||||
@@ -146,7 +140,11 @@ class SelfossModel {
|
|||||||
stringUrl = "http:$stringUrl"
|
stringUrl = "http:$stringUrl"
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (stringUrl.isEmptyOrNullOrNullString()) null else stringUrl
|
if (stringUrl.isEmptyOrNullOrNullString()) {
|
||||||
|
throw Exception("Link $link was translated to $stringUrl, but was empty. Handle this.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sourceAuthorAndDate(): String {
|
fun sourceAuthorAndDate(): String {
|
||||||
@@ -172,7 +170,7 @@ class SelfossModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this seems to be super slow.
|
// TODO: this seems to be super slow.
|
||||||
object TagsListSerializer : KSerializer<List<String>> {
|
object TagsListSerializer : KSerializer<List<String>> {
|
||||||
override fun deserialize(decoder: Decoder): List<String> =
|
override fun deserialize(decoder: Decoder): List<String> =
|
||||||
when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {
|
when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
@file:Suppress("detekt:TooManyFunctions")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.repository
|
package bou.amine.apps.readerforselfossv2.repository
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.dao.ACTION
|
import bou.amine.apps.readerforselfossv2.dao.ACTION
|
||||||
@@ -13,8 +11,7 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
|||||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import bou.amine.apps.readerforselfossv2.service.ConnectivityService
|
import bou.amine.apps.readerforselfossv2.utils.Enums.ItemType
|
||||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||||
import bou.amine.apps.readerforselfossv2.utils.toEntity
|
import bou.amine.apps.readerforselfossv2.utils.toEntity
|
||||||
import bou.amine.apps.readerforselfossv2.utils.toParsedDate
|
import bou.amine.apps.readerforselfossv2.utils.toParsedDate
|
||||||
@@ -26,44 +23,43 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
private const val MAX_ITEMS_NUMBER = 200
|
|
||||||
|
|
||||||
class Repository(
|
class Repository(
|
||||||
private val api: SelfossApi,
|
private val api: SelfossApi,
|
||||||
private val appSettingsService: AppSettingsService,
|
private val appSettingsService: AppSettingsService,
|
||||||
private val connectivityService: ConnectivityService,
|
val isConnectionAvailable: MutableStateFlow<Boolean>,
|
||||||
private val db: ReaderForSelfossDB,
|
private val db: ReaderForSelfossDB,
|
||||||
) {
|
) {
|
||||||
var items = ArrayList<SelfossModel.Item>()
|
var items = ArrayList<SelfossModel.Item>()
|
||||||
|
var connectionMonitored = false
|
||||||
|
|
||||||
var baseUrl = appSettingsService.getBaseUrl()
|
var baseUrl = appSettingsService.getBaseUrl()
|
||||||
|
|
||||||
var displayedItems = ItemType.UNREAD
|
var displayedItems = ItemType.UNREAD
|
||||||
|
|
||||||
private var _tagFilter = MutableStateFlow<SelfossModel.Tag?>(null)
|
private var tagFilterFlow = MutableStateFlow<SelfossModel.Tag?>(null)
|
||||||
var tagFilter = _tagFilter.asStateFlow()
|
var tagFilter = tagFilterFlow.asStateFlow()
|
||||||
private var _sourceFilter = MutableStateFlow<SelfossModel.Source?>(null)
|
private var sourceFilterFlow = MutableStateFlow<SelfossModel.Source?>(null)
|
||||||
var sourceFilter = _sourceFilter.asStateFlow()
|
var sourceFilter = sourceFilterFlow.asStateFlow()
|
||||||
var searchFilter: String? = null
|
var searchFilter: String? = null
|
||||||
|
|
||||||
var offlineOverride = false
|
var offlineOverride = false
|
||||||
|
|
||||||
private val _badgeUnread = MutableStateFlow(0)
|
private val badgeUnreadFlow = MutableStateFlow(0)
|
||||||
val badgeUnread = _badgeUnread.asStateFlow()
|
val badgeUnread = badgeUnreadFlow.asStateFlow()
|
||||||
private val _badgeAll = MutableStateFlow(0)
|
private val badgeAllFlow = MutableStateFlow(0)
|
||||||
val badgeAll = _badgeAll.asStateFlow()
|
val badgeAll = badgeAllFlow.asStateFlow()
|
||||||
private val _badgeStarred = MutableStateFlow(0)
|
private val badgeStarredFlow = MutableStateFlow(0)
|
||||||
val badgeStarred = _badgeStarred.asStateFlow()
|
val badgeStarred = badgeStarredFlow.asStateFlow()
|
||||||
|
|
||||||
private var fetchedTags = false
|
private var fetchedTags = false
|
||||||
private var fetchedSources = false
|
private var fetchedSources = false
|
||||||
|
|
||||||
private var _readerItems = ArrayList<SelfossModel.Item>()
|
private var readerItems = ArrayList<SelfossModel.Item>()
|
||||||
private var _selectedSource: SelfossModel.SourceDetail? = null
|
private var selectedSource: SelfossModel.SourceDetail? = null
|
||||||
|
|
||||||
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
||||||
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
fetchedItems =
|
fetchedItems =
|
||||||
api.getItems(
|
api.getItems(
|
||||||
displayedItems.type,
|
displayedItems.type,
|
||||||
@@ -102,7 +98,7 @@ class Repository(
|
|||||||
|
|
||||||
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
|
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
|
||||||
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
val offset = items.size
|
val offset = items.size
|
||||||
fetchedItems =
|
fetchedItems =
|
||||||
api.getItems(
|
api.getItems(
|
||||||
@@ -122,7 +118,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getMaxItemsForBackground(itemType: ItemType): List<SelfossModel.Item> {
|
private suspend fun getMaxItemsForBackground(itemType: ItemType): List<SelfossModel.Item> {
|
||||||
return if (connectivityService.isNetworkAvailable()) {
|
return if (isNetworkAvailable()) {
|
||||||
val items =
|
val items =
|
||||||
api.getItems(
|
api.getItems(
|
||||||
itemType.type,
|
itemType.type,
|
||||||
@@ -131,7 +127,7 @@ class Repository(
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
MAX_ITEMS_NUMBER,
|
200,
|
||||||
)
|
)
|
||||||
return if (items.success && items.data != null) {
|
return if (items.success && items.data != null) {
|
||||||
items.data
|
items.data
|
||||||
@@ -143,23 +139,22 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:ForbiddenComment")
|
|
||||||
suspend fun reloadBadges(): Boolean {
|
suspend fun reloadBadges(): Boolean {
|
||||||
var success = false
|
var success = false
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
val response = api.stats()
|
val response = api.stats()
|
||||||
if (response.success && response.data != null) {
|
if (response.success && response.data != null) {
|
||||||
_badgeUnread.value = response.data.unread ?: 0
|
badgeUnreadFlow.value = response.data.unread ?: 0
|
||||||
_badgeAll.value = response.data.total
|
badgeAllFlow.value = response.data.total
|
||||||
_badgeStarred.value = response.data.starred ?: 0
|
badgeStarredFlow.value = response.data.starred ?: 0
|
||||||
success = true
|
success = true
|
||||||
}
|
}
|
||||||
} else if (appSettingsService.isItemCachingEnabled()) {
|
} else if (appSettingsService.isItemCachingEnabled()) {
|
||||||
// TODO: do this differently, because it's not efficient
|
// TODO: do this differently, because it's not efficient
|
||||||
val dbItems = getDBItems()
|
val dbItems = getDBItems()
|
||||||
_badgeUnread.value = dbItems.filter { item -> item.unread }.size
|
badgeUnreadFlow.value = dbItems.filter { item -> item.unread }.size
|
||||||
_badgeStarred.value = dbItems.filter { item -> item.starred }.size
|
badgeStarredFlow.value = dbItems.filter { item -> item.starred }.size
|
||||||
_badgeAll.value = dbItems.size
|
badgeAllFlow.value = dbItems.size
|
||||||
success = true
|
success = true
|
||||||
}
|
}
|
||||||
return success
|
return success
|
||||||
@@ -168,7 +163,7 @@ class Repository(
|
|||||||
suspend fun getTags(): List<SelfossModel.Tag> {
|
suspend fun getTags(): List<SelfossModel.Tag> {
|
||||||
val isDatabaseEnabled =
|
val isDatabaseEnabled =
|
||||||
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
|
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
|
||||||
return if (connectivityService.isNetworkAvailable() && !fetchedTags) {
|
return if (isNetworkAvailable() && !fetchedTags) {
|
||||||
val apiTags = api.tags()
|
val apiTags = api.tags()
|
||||||
if (apiTags.success && apiTags.data != null && isDatabaseEnabled) {
|
if (apiTags.success && apiTags.data != null && isDatabaseEnabled) {
|
||||||
resetDBTagsWithData(apiTags.data)
|
resetDBTagsWithData(apiTags.data)
|
||||||
@@ -185,7 +180,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSpouts(): Map<String, SelfossModel.Spout> =
|
suspend fun getSpouts(): Map<String, SelfossModel.Spout> =
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
val spouts = api.spouts()
|
val spouts = api.spouts()
|
||||||
if (spouts.success && spouts.data != null) {
|
if (spouts.success && spouts.data != null) {
|
||||||
spouts.data
|
spouts.data
|
||||||
@@ -201,7 +196,7 @@ class Repository(
|
|||||||
val isDatabaseEnabled =
|
val isDatabaseEnabled =
|
||||||
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
|
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
|
||||||
val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true
|
val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true
|
||||||
if (shouldFetch && connectivityService.isNetworkAvailable()) {
|
if (shouldFetch && isNetworkAvailable()) {
|
||||||
if (appSettingsService.getPublicAccess()) {
|
if (appSettingsService.getPublicAccess()) {
|
||||||
val apiSources = api.sourcesStats()
|
val apiSources = api.sourcesStats()
|
||||||
if (apiSources.success && apiSources.data != null) {
|
if (apiSources.success && apiSources.data != null) {
|
||||||
@@ -223,7 +218,7 @@ class Repository(
|
|||||||
val isDatabaseEnabled =
|
val isDatabaseEnabled =
|
||||||
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
|
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
|
||||||
val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true
|
val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true
|
||||||
if (shouldFetch && connectivityService.isNetworkAvailable()) {
|
if (shouldFetch && isNetworkAvailable()) {
|
||||||
val apiSources = api.sourcesDetailed()
|
val apiSources = api.sourcesDetailed()
|
||||||
if (apiSources.success && apiSources.data != null) {
|
if (apiSources.success && apiSources.data != null) {
|
||||||
fetchedSources = true
|
fetchedSources = true
|
||||||
@@ -248,7 +243,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun markAsReadById(id: Int): Boolean =
|
private suspend fun markAsReadById(id: Int): Boolean =
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.markAsRead(id.toString()).isSuccess
|
api.markAsRead(id.toString()).isSuccess
|
||||||
} else {
|
} else {
|
||||||
insertDBAction(id.toString(), read = true)
|
insertDBAction(id.toString(), read = true)
|
||||||
@@ -265,7 +260,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun unmarkAsReadById(id: Int): Boolean =
|
private suspend fun unmarkAsReadById(id: Int): Boolean =
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.unmarkAsRead(id.toString()).isSuccess
|
api.unmarkAsRead(id.toString()).isSuccess
|
||||||
} else {
|
} else {
|
||||||
insertDBAction(id.toString(), unread = true)
|
insertDBAction(id.toString(), unread = true)
|
||||||
@@ -282,7 +277,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun starrById(id: Int): Boolean =
|
private suspend fun starrById(id: Int): Boolean =
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.starr(id.toString()).isSuccess
|
api.starr(id.toString()).isSuccess
|
||||||
} else {
|
} else {
|
||||||
insertDBAction(id.toString(), starred = true)
|
insertDBAction(id.toString(), starred = true)
|
||||||
@@ -299,7 +294,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun unstarrById(id: Int): Boolean =
|
private suspend fun unstarrById(id: Int): Boolean =
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.unstarr(id.toString()).isSuccess
|
api.unstarr(id.toString()).isSuccess
|
||||||
} else {
|
} else {
|
||||||
insertDBAction(id.toString(), starred = true)
|
insertDBAction(id.toString(), starred = true)
|
||||||
@@ -309,8 +304,7 @@ class Repository(
|
|||||||
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
||||||
var success = false
|
var success = false
|
||||||
|
|
||||||
if (connectivityService.isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() }).isSuccess
|
if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() }).isSuccess) {
|
||||||
) {
|
|
||||||
success = true
|
success = true
|
||||||
for (item in items) {
|
for (item in items) {
|
||||||
markAsReadLocally(item)
|
markAsReadLocally(item)
|
||||||
@@ -322,7 +316,7 @@ class Repository(
|
|||||||
private fun markAsReadLocally(item: SelfossModel.Item) {
|
private fun markAsReadLocally(item: SelfossModel.Item) {
|
||||||
if (item.unread) {
|
if (item.unread) {
|
||||||
item.unread = false
|
item.unread = false
|
||||||
_badgeUnread.value -= 1
|
badgeUnreadFlow.value -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@@ -333,7 +327,7 @@ class Repository(
|
|||||||
private fun unmarkAsReadLocally(item: SelfossModel.Item) {
|
private fun unmarkAsReadLocally(item: SelfossModel.Item) {
|
||||||
if (!item.unread) {
|
if (!item.unread) {
|
||||||
item.unread = true
|
item.unread = true
|
||||||
_badgeUnread.value += 1
|
badgeUnreadFlow.value += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@@ -344,7 +338,7 @@ class Repository(
|
|||||||
private fun starrLocally(item: SelfossModel.Item) {
|
private fun starrLocally(item: SelfossModel.Item) {
|
||||||
if (!item.starred) {
|
if (!item.starred) {
|
||||||
item.starred = true
|
item.starred = true
|
||||||
_badgeStarred.value += 1
|
badgeStarredFlow.value += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@@ -355,7 +349,7 @@ class Repository(
|
|||||||
private fun unstarrLocally(item: SelfossModel.Item) {
|
private fun unstarrLocally(item: SelfossModel.Item) {
|
||||||
if (item.starred) {
|
if (item.starred) {
|
||||||
item.starred = false
|
item.starred = false
|
||||||
_badgeStarred.value -= 1
|
badgeStarredFlow.value -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@@ -370,7 +364,7 @@ class Repository(
|
|||||||
tags: String,
|
tags: String,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
var response = false
|
var response = false
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
response = api
|
response = api
|
||||||
.createSourceForVersion(
|
.createSourceForVersion(
|
||||||
title,
|
title,
|
||||||
@@ -391,7 +385,7 @@ class Repository(
|
|||||||
tags: String,
|
tags: String,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
var response = false
|
var response = false
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
response = api.updateSourceForVersion(id, title, url, spout, tags).isSuccess == true
|
response = api.updateSourceForVersion(id, title, url, spout, tags).isSuccess == true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,13 +397,13 @@ class Repository(
|
|||||||
title: String,
|
title: String,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
var success = false
|
var success = false
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
val response = api.deleteSource(id)
|
val response = api.deleteSource(id)
|
||||||
success = response.isSuccess
|
success = response.isSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
// We filter on success or if the network isn't available
|
// We filter on success or if the network isn't available
|
||||||
if (success || !connectivityService.isNetworkAvailable()) {
|
if (success || !isNetworkAvailable()) {
|
||||||
items = ArrayList(items.filter { it.sourcetitle != title })
|
items = ArrayList(items.filter { it.sourcetitle != title })
|
||||||
setReaderItems(items)
|
setReaderItems(items)
|
||||||
db.itemsQueries.deleteItemsWhereSource(title)
|
db.itemsQueries.deleteItemsWhereSource(title)
|
||||||
@@ -419,7 +413,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateRemote(): Boolean =
|
suspend fun updateRemote(): Boolean =
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.update().data.equals("finished")
|
api.update().data.equals("finished")
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@@ -427,7 +421,7 @@ class Repository(
|
|||||||
|
|
||||||
suspend fun login(): Boolean {
|
suspend fun login(): Boolean {
|
||||||
var result = false
|
var result = false
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
try {
|
try {
|
||||||
val response = api.login()
|
val response = api.login()
|
||||||
result = response.isSuccess == true
|
result = response.isSuccess == true
|
||||||
@@ -440,7 +434,7 @@ class Repository(
|
|||||||
|
|
||||||
suspend fun checkIfFetchFails(): Boolean {
|
suspend fun checkIfFetchFails(): Boolean {
|
||||||
var fetchFailed = true
|
var fetchFailed = true
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
try {
|
try {
|
||||||
// Trying to fetch one item, and check someone is trying to use the app with
|
// Trying to fetch one item, and check someone is trying to use the app with
|
||||||
// a random rss feed, that would throw a NoTransformationFoundException
|
// a random rss feed, that would throw a NoTransformationFoundException
|
||||||
@@ -454,7 +448,7 @@ class Repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun logout() {
|
suspend fun logout() {
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
try {
|
try {
|
||||||
val response = api.logout()
|
val response = api.logout()
|
||||||
if (!response.isSuccess) {
|
if (!response.isSuccess) {
|
||||||
@@ -482,7 +476,7 @@ class Repository(
|
|||||||
suspend fun updateApiInformation() {
|
suspend fun updateApiInformation() {
|
||||||
val apiMajorVersion = appSettingsService.getApiVersion()
|
val apiMajorVersion = appSettingsService.getApiVersion()
|
||||||
|
|
||||||
if (connectivityService.isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
val fetchedInformation = api.apiInformation()
|
val fetchedInformation = api.apiInformation()
|
||||||
if (fetchedInformation.success && fetchedInformation.data != null) {
|
if (fetchedInformation.success && fetchedInformation.data != null) {
|
||||||
if (fetchedInformation.data.getApiMajorVersion() != apiMajorVersion) {
|
if (fetchedInformation.data.getApiMajorVersion() != apiMajorVersion) {
|
||||||
@@ -501,6 +495,8 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride
|
||||||
|
|
||||||
private fun getDBActions(): List<ACTION> = db.actionsQueries.actions().executeAsList()
|
private fun getDBActions(): List<ACTION> = db.actionsQueries.actions().executeAsList()
|
||||||
|
|
||||||
private fun deleteDBAction(action: ACTION) = db.actionsQueries.deleteAction(action.id)
|
private fun deleteDBAction(action: ACTION) = db.actionsQueries.deleteAction(action.id)
|
||||||
@@ -563,7 +559,6 @@ class Repository(
|
|||||||
item.id.toString(),
|
item.id.toString(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
|
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
|
||||||
try {
|
try {
|
||||||
val newItems = getMaxItemsForBackground(ItemType.UNREAD)
|
val newItems = getMaxItemsForBackground(ItemType.UNREAD)
|
||||||
@@ -619,30 +614,30 @@ class Repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setTagFilter(tag: SelfossModel.Tag?) {
|
fun setTagFilter(tag: SelfossModel.Tag?) {
|
||||||
_tagFilter.value = tag
|
tagFilterFlow.value = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSourceFilter(source: SelfossModel.Source?) {
|
fun setSourceFilter(source: SelfossModel.Source?) {
|
||||||
_sourceFilter.value = source
|
sourceFilterFlow.value = source
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setReaderItems(readerItems: ArrayList<SelfossModel.Item>) {
|
fun setReaderItems(readerItems: ArrayList<SelfossModel.Item>) {
|
||||||
_readerItems = readerItems
|
this.readerItems = readerItems
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getReaderItems(): ArrayList<SelfossModel.Item> = _readerItems
|
fun getReaderItems(): ArrayList<SelfossModel.Item> = readerItems
|
||||||
|
|
||||||
fun migrate(driverFactory: DriverFactory) {
|
fun migrate(driverFactory: DriverFactory) {
|
||||||
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSelectedSource(source: SelfossModel.SourceDetail) {
|
fun setSelectedSource(source: SelfossModel.SourceDetail) {
|
||||||
_selectedSource = source
|
selectedSource = source
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unsetSelectedSource() {
|
fun unsetSelectedSource() {
|
||||||
_selectedSource = null
|
selectedSource = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSelectedSource(): SelfossModel.SourceDetail? = _selectedSource
|
fun getSelectedSource(): SelfossModel.SourceDetail? = selectedSource
|
||||||
}
|
}
|
||||||
|
@@ -17,8 +17,8 @@ import kotlinx.serialization.json.Json
|
|||||||
class MercuryApi {
|
class MercuryApi {
|
||||||
var client = createHttpClient()
|
var client = createHttpClient()
|
||||||
|
|
||||||
private fun createHttpClient(): HttpClient =
|
private fun createHttpClient(): HttpClient {
|
||||||
HttpClient {
|
return HttpClient {
|
||||||
install(HttpCache)
|
install(HttpCache)
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
json(
|
json(
|
||||||
@@ -40,6 +40,7 @@ class MercuryApi {
|
|||||||
}
|
}
|
||||||
expectSuccess = false
|
expectSuccess = false
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> =
|
suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> =
|
||||||
bodyOrFailure(
|
bodyOrFailure(
|
||||||
|
@@ -33,7 +33,6 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse =
|
|||||||
SuccessResponse(false)
|
SuccessResponse(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
|
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
|
||||||
try {
|
try {
|
||||||
return if (r != null && r.status.isSuccess()) {
|
return if (r != null && r.status.isSuccess()) {
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
@file:Suppress("detekt:TooManyFunctions", "detekt:LongParameterList", "detekt:LargeClass")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.rest
|
package bou.amine.apps.readerforselfossv2.rest
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
@@ -37,8 +35,6 @@ import kotlinx.serialization.json.Json
|
|||||||
|
|
||||||
expect fun setupInsecureHttpEngine(config: CIOEngineConfig)
|
expect fun setupInsecureHttpEngine(config: CIOEngineConfig)
|
||||||
|
|
||||||
private const val VERSION_WHERE_POST_LOGIN_SHOULD_WORK = 5
|
|
||||||
|
|
||||||
class SelfossApi(
|
class SelfossApi(
|
||||||
private val appSettingsService: AppSettingsService,
|
private val appSettingsService: AppSettingsService,
|
||||||
) {
|
) {
|
||||||
@@ -180,7 +176,7 @@ class SelfossApi(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= VERSION_WHERE_POST_LOGIN_SHOULD_WORK // We are missing 4.1.0
|
private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
|
||||||
|
|
||||||
suspend fun logout(): SuccessResponse =
|
suspend fun logout(): SuccessResponse =
|
||||||
if (shouldHaveNewLogout()) {
|
if (shouldHaveNewLogout()) {
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
@file:Suppress("detekt:TooManyFunctions")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.service
|
package bou.amine.apps.readerforselfossv2.service
|
||||||
|
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
|
@@ -1,19 +1,7 @@
|
|||||||
@file:Suppress("detekt:TooManyFunctions")
|
|
||||||
|
|
||||||
package bou.amine.apps.readerforselfossv2.service
|
package bou.amine.apps.readerforselfossv2.service
|
||||||
|
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
|
|
||||||
private const val DEFAULT_FONT_SIZE = 16
|
|
||||||
|
|
||||||
private const val DEFAULT_REFRESH_MINUTES = 360L
|
|
||||||
|
|
||||||
private const val MIN_REFRESH_MINUTES = 15L
|
|
||||||
|
|
||||||
private const val DEFAULT_API_TIMEOUT = 60L
|
|
||||||
|
|
||||||
private const val DEFAULT_ITEMS_NUMBER = 20
|
|
||||||
|
|
||||||
class AppSettingsService(
|
class AppSettingsService(
|
||||||
acraSenderServiceProcess: Boolean = false,
|
acraSenderServiceProcess: Boolean = false,
|
||||||
) {
|
) {
|
||||||
@@ -48,11 +36,12 @@ class AppSettingsService(
|
|||||||
private var notifyNewItems: Boolean? = null
|
private var notifyNewItems: Boolean? = null
|
||||||
private var itemsNumber: Int? = null
|
private var itemsNumber: Int? = null
|
||||||
private var apiTimeout: Long? = null
|
private var apiTimeout: Long? = null
|
||||||
private var refreshMinutes: Long = DEFAULT_REFRESH_MINUTES
|
private var refreshMinutes: Long = 360
|
||||||
private var markOnScroll: Boolean? = null
|
private var markOnScroll: Boolean? = null
|
||||||
private var activeAlignment: Int? = null
|
private var activeAlignment: Int? = null
|
||||||
|
|
||||||
private var fontSize: Int? = null
|
private var fontSize: Int? = null
|
||||||
|
private var staticBar: Boolean? = null
|
||||||
private var font: String = ""
|
private var font: String = ""
|
||||||
private var theme: Int? = null
|
private var theme: Int? = null
|
||||||
|
|
||||||
@@ -152,14 +141,13 @@ class AppSettingsService(
|
|||||||
return itemsNumber!!
|
return itemsNumber!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
private fun refreshItemsNumber() {
|
private fun refreshItemsNumber() {
|
||||||
itemsNumber =
|
itemsNumber =
|
||||||
try {
|
try {
|
||||||
settings.getString(API_ITEMS_NUMBER, DEFAULT_ITEMS_NUMBER.toString()).toInt()
|
settings.getString(API_ITEMS_NUMBER, "20").toInt()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
settings.remove(API_ITEMS_NUMBER)
|
settings.remove(API_ITEMS_NUMBER)
|
||||||
DEFAULT_ITEMS_NUMBER
|
20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,24 +158,22 @@ class AppSettingsService(
|
|||||||
return apiTimeout!!
|
return apiTimeout!!
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:MagicNumber")
|
|
||||||
private fun secToMs(n: Long) = n * 1000
|
private fun secToMs(n: Long) = n * 1000
|
||||||
|
|
||||||
@Suppress("detekt:SwallowedException")
|
|
||||||
private fun refreshApiTimeout() {
|
private fun refreshApiTimeout() {
|
||||||
apiTimeout =
|
apiTimeout =
|
||||||
secToMs(
|
secToMs(
|
||||||
try {
|
try {
|
||||||
val settingsTimeout = settings.getString(API_TIMEOUT, DEFAULT_API_TIMEOUT.toString())
|
val settingsTimeout = settings.getString(API_TIMEOUT, "60")
|
||||||
if (settingsTimeout.toLong() > 0) {
|
if (settingsTimeout.toLong() > 0) {
|
||||||
settingsTimeout.toLong()
|
settingsTimeout.toLong()
|
||||||
} else {
|
} else {
|
||||||
settings.remove(API_TIMEOUT)
|
settings.remove(API_TIMEOUT)
|
||||||
DEFAULT_API_TIMEOUT
|
60
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
settings.remove(API_TIMEOUT)
|
settings.remove(API_TIMEOUT)
|
||||||
DEFAULT_API_TIMEOUT
|
60
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -301,14 +287,14 @@ class AppSettingsService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshRefreshMinutes() {
|
private fun refreshRefreshMinutes() {
|
||||||
refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, DEFAULT_REFRESH_MINUTES.toString()).toLong()
|
refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, "360").toLong()
|
||||||
if (refreshMinutes <= MIN_REFRESH_MINUTES) {
|
if (refreshMinutes <= 15) {
|
||||||
refreshMinutes = MIN_REFRESH_MINUTES
|
refreshMinutes = 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRefreshMinutes(): Long {
|
fun getRefreshMinutes(): Long {
|
||||||
if (refreshMinutes != DEFAULT_REFRESH_MINUTES) {
|
if (refreshMinutes != 360L) {
|
||||||
refreshRefreshMinutes()
|
refreshRefreshMinutes()
|
||||||
}
|
}
|
||||||
return refreshMinutes
|
return refreshMinutes
|
||||||
@@ -382,7 +368,18 @@ class AppSettingsService(
|
|||||||
if (fontSize != null) {
|
if (fontSize != null) {
|
||||||
refreshFontSize()
|
refreshFontSize()
|
||||||
}
|
}
|
||||||
return fontSize ?: DEFAULT_FONT_SIZE
|
return fontSize ?: 16
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshStaticBarEnabled() {
|
||||||
|
staticBar = settings.getBoolean(READER_STATIC_BAR, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isStaticBarEnabled(): Boolean {
|
||||||
|
if (staticBar != null) {
|
||||||
|
refreshStaticBarEnabled()
|
||||||
|
}
|
||||||
|
return staticBar == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshFont() {
|
private fun refreshFont() {
|
||||||
@@ -437,6 +434,7 @@ class AppSettingsService(
|
|||||||
refreshActiveAllignment()
|
refreshActiveAllignment()
|
||||||
refreshFontSize()
|
refreshFontSize()
|
||||||
refreshFont()
|
refreshFont()
|
||||||
|
refreshStaticBarEnabled()
|
||||||
refreshCurrentTheme()
|
refreshCurrentTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,11 +484,11 @@ class AppSettingsService(
|
|||||||
|
|
||||||
const val SOURCE_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform"
|
const val SOURCE_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform"
|
||||||
|
|
||||||
const val BUG_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
const val TRACKER_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
||||||
|
|
||||||
const val SYNC_CHANNEL_ID = "sync-channel-id"
|
const val SYNC_CHANNEL_ID = "sync-channel-id"
|
||||||
|
|
||||||
const val NEW_ITEMS_CHANNEL = "new-items-channel-id"
|
const val NEW_ITEMS_CHANNEL_ID = "new-items-channel-id"
|
||||||
|
|
||||||
const val JUSTIFY = 1
|
const val JUSTIFY = 1
|
||||||
|
|
||||||
@@ -534,6 +532,8 @@ class AppSettingsService(
|
|||||||
|
|
||||||
const val READER_FONT = "reader_font"
|
const val READER_FONT = "reader_font"
|
||||||
|
|
||||||
|
const val READER_STATIC_BAR = "reader_static_bar"
|
||||||
|
|
||||||
const val READER_FONT_SIZE = "reader_font_size"
|
const val READER_FONT_SIZE = "reader_font_size"
|
||||||
|
|
||||||
const val TEXT_ALIGN = "text_align"
|
const val TEXT_ALIGN = "text_align"
|
||||||
|
@@ -1,46 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.service
|
|
||||||
|
|
||||||
import dev.jordond.connectivity.Connectivity
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class ConnectivityService {
|
|
||||||
private val _networkAvailableProvider = MutableSharedFlow<Boolean>()
|
|
||||||
val networkAvailableProvider = _networkAvailableProvider.asSharedFlow()
|
|
||||||
private var currentStatus = true
|
|
||||||
private lateinit var connectivity: Connectivity
|
|
||||||
|
|
||||||
fun start() {
|
|
||||||
connectivity = Connectivity()
|
|
||||||
connectivity.start()
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
|
||||||
connectivity.statusUpdates.collect { status ->
|
|
||||||
when (status) {
|
|
||||||
is Connectivity.Status.Connected -> {
|
|
||||||
if (!currentStatus) {
|
|
||||||
currentStatus = true
|
|
||||||
_networkAvailableProvider.emit(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is Connectivity.Status.Disconnected -> {
|
|
||||||
if (currentStatus) {
|
|
||||||
currentStatus = false
|
|
||||||
_networkAvailableProvider.emit(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isNetworkAvailable(): Boolean = currentStatus
|
|
||||||
|
|
||||||
fun stop() {
|
|
||||||
currentStatus = true
|
|
||||||
connectivity.stop()
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,12 +4,6 @@ import kotlinx.datetime.LocalDateTime
|
|||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.datetime.toInstant
|
import kotlinx.datetime.toInstant
|
||||||
|
|
||||||
class DateParseException(
|
|
||||||
message: String,
|
|
||||||
e: Throwable? = null,
|
|
||||||
) : Throwable(message, e)
|
|
||||||
|
|
||||||
@Suppress("detekt:ThrowsCount")
|
|
||||||
fun String.toParsedDate(): Long {
|
fun String.toParsedDate(): Long {
|
||||||
// Possible formats are
|
// Possible formats are
|
||||||
// yyyy-mm-dd hh:mm:ss format
|
// yyyy-mm-dd hh:mm:ss format
|
||||||
@@ -23,22 +17,17 @@ fun String.toParsedDate(): Long {
|
|||||||
if (this.matches(oldVersionFormat)) {
|
if (this.matches(oldVersionFormat)) {
|
||||||
this.replace(" ", "T")
|
this.replace(" ", "T")
|
||||||
} else if (this.matches(newVersionFormat)) {
|
} else if (this.matches(newVersionFormat)) {
|
||||||
newVersionFormat
|
newVersionFormat.find(this)?.groups?.get(1)?.value ?: throw Exception("Couldn't parse $this")
|
||||||
.find(this)
|
|
||||||
?.groups
|
|
||||||
?.get(1)
|
|
||||||
?.value ?: throw DateParseException("Couldn't parse $this")
|
|
||||||
} else {
|
} else {
|
||||||
throw DateParseException("Unrecognized format for $this")
|
throw Exception("Unrecognized format for $this")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw DateParseException("parseDate failed for $this", e)
|
throw Exception("parseDate failed for $this", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
|
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
|
||||||
expect class DateUtils() {
|
expect class DateUtils() {
|
||||||
companion object {
|
companion object {
|
||||||
fun parseRelativeDate(dateString: String): String
|
fun parseRelativeDate(dateString: String): String
|
||||||
|
@@ -17,7 +17,7 @@ fun SOURCE.toView(): SelfossModel.SourceDetail =
|
|||||||
this.id.toInt(),
|
this.id.toInt(),
|
||||||
this.title,
|
this.title,
|
||||||
null,
|
null,
|
||||||
this.tags.split(","),
|
this.tags?.split(","),
|
||||||
this.spout,
|
this.spout,
|
||||||
this.error,
|
this.error,
|
||||||
this.icon,
|
this.icon,
|
||||||
@@ -74,7 +74,6 @@ fun SelfossModel.Item.toEntity(): ITEM =
|
|||||||
this.author,
|
this.author,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("detekt:MagicNumber")
|
|
||||||
fun SelfossModel.Tag.getColorHexCode(): String =
|
fun SelfossModel.Tag.getColorHexCode(): String =
|
||||||
if (this.color.length == 4) { // #000
|
if (this.color.length == 4) { // #000
|
||||||
val char1 = this.color.get(1)
|
val char1 = this.color.get(1)
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
|
object Enums {
|
||||||
|
enum class ItemType(
|
||||||
|
val position: Int,
|
||||||
|
val type: String,
|
||||||
|
) {
|
||||||
|
UNREAD(1, "unread"),
|
||||||
|
ALL(2, "all"),
|
||||||
|
STARRED(3, "starred"),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromInt(value: Int) = values().first { it.position == value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,16 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
|
||||||
|
|
||||||
@Suppress("detekt:MagicNumber")
|
|
||||||
enum class ItemType(
|
|
||||||
val position: Int,
|
|
||||||
val type: String,
|
|
||||||
) {
|
|
||||||
UNREAD(1, "unread"),
|
|
||||||
ALL(2, "all"),
|
|
||||||
STARRED(3, "starred"),
|
|
||||||
;
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromInt(value: Int) = values().first { it.position == value }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -2,7 +2,6 @@ package bou.amine.apps.readerforselfossv2.utils
|
|||||||
|
|
||||||
fun String?.isEmptyOrNullOrNullString(): Boolean = this == null || this == "null" || this.isEmpty()
|
fun String?.isEmptyOrNullOrNullString(): Boolean = this == null || this == "null" || this.isEmpty()
|
||||||
|
|
||||||
@Suppress("detekt:MagicNumber")
|
|
||||||
fun String.longHash(): Long {
|
fun String.longHash(): Long {
|
||||||
var h = 98764321261L
|
var h = 98764321261L
|
||||||
val l = this.length
|
val l = this.length
|
||||||
|
@@ -18,8 +18,7 @@ class DatesTest {
|
|||||||
fun new_version_date_should_be_parsed() {
|
fun new_version_date_should_be_parsed() {
|
||||||
val date = newVersionDate.toParsedDate()
|
val date = newVersionDate.toParsedDate()
|
||||||
val expected =
|
val expected =
|
||||||
LocalDateTime(2013, 4, 7, 13, 43, 0, 0)
|
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||||
.toInstant(TimeZone.currentSystemDefault())
|
|
||||||
.toEpochMilliseconds()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
||||||
@@ -29,8 +28,7 @@ class DatesTest {
|
|||||||
fun new_version_date2_should_be_parsed() {
|
fun new_version_date2_should_be_parsed() {
|
||||||
val date = newVersionDate2.toParsedDate()
|
val date = newVersionDate2.toParsedDate()
|
||||||
val expected =
|
val expected =
|
||||||
LocalDateTime(2013, 4, 7, 13, 43, 0, 0)
|
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||||
.toInstant(TimeZone.currentSystemDefault())
|
|
||||||
.toEpochMilliseconds()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
||||||
@@ -40,8 +38,7 @@ class DatesTest {
|
|||||||
fun old_version_date_should_be_parsed() {
|
fun old_version_date_should_be_parsed() {
|
||||||
val date = oldVersionDate.toParsedDate()
|
val date = oldVersionDate.toParsedDate()
|
||||||
val expected =
|
val expected =
|
||||||
LocalDateTime(2013, 5, 7, 13, 46, 0, 0)
|
LocalDateTime(2013, 5, 7, 13, 46, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||||
.toInstant(TimeZone.currentSystemDefault())
|
|
||||||
.toEpochMilliseconds()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
||||||
@@ -51,8 +48,7 @@ class DatesTest {
|
|||||||
fun old_version_variant_date_should_be_parsed() {
|
fun old_version_variant_date_should_be_parsed() {
|
||||||
val date = oldVersionDateVariant.toParsedDate()
|
val date = oldVersionDateVariant.toParsedDate()
|
||||||
val expected =
|
val expected =
|
||||||
LocalDateTime(2021, 3, 21, 10, 32, 0, 0)
|
LocalDateTime(2021, 3, 21, 10, 32, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||||
.toInstant(TimeZone.currentSystemDefault())
|
|
||||||
.toEpochMilliseconds()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
||||||
@@ -62,8 +58,7 @@ class DatesTest {
|
|||||||
fun new_version_variant_date_should_be_parsed() {
|
fun new_version_variant_date_should_be_parsed() {
|
||||||
val date = newVersionDateVariant.toParsedDate()
|
val date = newVersionDateVariant.toParsedDate()
|
||||||
val expected =
|
val expected =
|
||||||
LocalDateTime(2022, 12, 24, 17, 0, 8, 0)
|
LocalDateTime(2022, 12, 24, 17, 0, 8, 0).toInstant(TimeZone.currentSystemDefault())
|
||||||
.toInstant(TimeZone.currentSystemDefault())
|
|
||||||
.toEpochMilliseconds()
|
.toEpochMilliseconds()
|
||||||
|
|
||||||
assertEquals(expected, date)
|
assertEquals(expected, date)
|
@@ -4,5 +4,7 @@ import app.cash.sqldelight.db.SqlDriver
|
|||||||
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
||||||
|
|
||||||
actual class DriverFactory {
|
actual class DriverFactory {
|
||||||
actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
actual fun createDriver(): SqlDriver {
|
||||||
|
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,5 @@ package bou.amine.apps.readerforselfossv2.rest
|
|||||||
|
|
||||||
import io.ktor.client.engine.cio.CIOEngineConfig
|
import io.ktor.client.engine.cio.CIOEngineConfig
|
||||||
|
|
||||||
@Suppress("detekt:EmptyFunctionBlock")
|
|
||||||
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
|
||||||
actual class DateUtils {
|
actual class DateUtils {
|
||||||
actual companion object {
|
actual companion object {
|
||||||
actual fun parseRelativeDate(dateString: String): String {
|
actual fun parseRelativeDate(dateString: String): String {
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
@Suppress("detekt:UtilityClassWithPublicConstructor")
|
|
||||||
actual class DateUtils actual constructor() {
|
actual class DateUtils actual constructor() {
|
||||||
actual companion object {
|
actual companion object {
|
||||||
actual fun parseRelativeDate(dateString: String): String {
|
actual fun parseRelativeDate(dateString: String): String {
|
@@ -4,5 +4,7 @@ import app.cash.sqldelight.db.SqlDriver
|
|||||||
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
import app.cash.sqldelight.driver.native.NativeSqliteDriver
|
||||||
|
|
||||||
actual class DriverFactory {
|
actual class DriverFactory {
|
||||||
actual fun createDriver(): SqlDriver = NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
actual fun createDriver(): SqlDriver {
|
||||||
|
return NativeSqliteDriver(ReaderForSelfossDB.Schema, "ReaderForSelfossV2-IOS.db")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,5 @@ package bou.amine.apps.readerforselfossv2.rest
|
|||||||
|
|
||||||
import io.ktor.client.engine.cio.CIOEngineConfig
|
import io.ktor.client.engine.cio.CIOEngineConfig
|
||||||
|
|
||||||
@Suppress("detekt:EmptyFunctionBlock")
|
|
||||||
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user