diff --git a/app/build.gradle b/app/build.gradle
index ebbfa12d6..2e8506008 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -226,20 +226,14 @@ android {
testBuildType "debug"
lintOptions {
- // Treat lint seriously
abortOnError true
- // Hardcoded strings in xml
- error "HardcodedText"
- // Hardcoded strings in setText
- error "SetTextI18n"
- // Strings.xml issues
- warning "ExtraTranslation"
- warning "MissingTranslation"
-
- // Warnings
- warning "InvalidPackage"
- warning "StringFormatInvalid"
+ checkAllWarnings true
+ warningsAsErrors true
+ ignore 'MissingTranslation', //TODO stop ignoring
+ 'CheckResult'
+ baseline file("lint-baseline.xml")
}
+
testOptions {
unitTests.returnDefaultValues = true
unitTests.all {
diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
new file mode 100644
index 000000000..4b9c062ff
--- /dev/null
+++ b/app/lint-baseline.xml
@@ -0,0 +1,16748 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/objectbox-models/default.json.bak b/app/objectbox-models/default.json.bak
index d51d2ba30..757048b9f 100644
--- a/app/objectbox-models/default.json.bak
+++ b/app/objectbox-models/default.json.bak
@@ -218,7 +218,7 @@
},
{
"id": "2:6862771806221961183",
- "name": "zimID"
+ "name": "zimId"
},
{
"id": "3:4312769031500860715",
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/di/components/ActivityComponent.kt b/app/src/main/java/org/kiwix/kiwixmobile/di/components/ActivityComponent.kt
index 70350b159..388c5397e 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/di/components/ActivityComponent.kt
+++ b/app/src/main/java/org/kiwix/kiwixmobile/di/components/ActivityComponent.kt
@@ -23,6 +23,7 @@ import dagger.Subcomponent
import org.kiwix.kiwixmobile.di.modules.ActivityModule
import org.kiwix.kiwixmobile.downloader.DownloadFragment
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.ZimFileSelectFragment
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.DeleteFiles
import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment
@Subcomponent(modules = [ActivityModule::class])
@@ -33,6 +34,8 @@ interface ActivityComponent {
fun inject(zimFileSelectFragment: ZimFileSelectFragment)
+ fun inject(deleteFiles: DeleteFiles)
+
@Subcomponent.Builder
interface Builder {
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadModel.kt b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadModel.kt
index 8a343797c..85d8277e2 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadModel.kt
+++ b/app/src/main/java/org/kiwix/kiwixmobile/downloader/model/DownloadModel.kt
@@ -18,10 +18,13 @@
package org.kiwix.kiwixmobile.downloader.model
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
+import org.kiwix.kiwixmobile.utils.StorageUtils
data class DownloadModel(
val databaseId: Long? = null,
- val downloadId: Long ,
+ val downloadId: Long,
val book: Book
-)
+) {
+ val fileNameFromUrl: String get() = StorageUtils.getFileNameFromUrl(book.url)
+}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/extensions/ActivityExtensions.kt b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ActivityExtensions.kt
new file mode 100644
index 000000000..19f27325b
--- /dev/null
+++ b/app/src/main/java/org/kiwix/kiwixmobile/extensions/ActivityExtensions.kt
@@ -0,0 +1,43 @@
+package org.kiwix.kiwixmobile.extensions
+
+import android.app.Activity
+import android.view.ActionMode
+import android.view.ActionMode.Callback
+import android.view.Menu
+import android.view.MenuItem
+
+fun Activity.startActionMode(
+ menuId: Int,
+ idsToClickActions: Map Any>,
+ onDestroyAction: () -> Unit
+): ActionMode? {
+ return startActionMode(object : Callback {
+ override fun onActionItemClicked(
+ mode: ActionMode,
+ item: MenuItem
+ ) = idsToClickActions[item.itemId]?.let {
+ it()
+ mode.finish()
+ true
+ } ?: false
+
+ override fun onCreateActionMode(
+ mode: ActionMode,
+ menu: Menu?
+ ): Boolean {
+ mode.getMenuInflater()
+ .inflate(menuId, menu)
+ return true
+ }
+
+ override fun onPrepareActionMode(
+ mode: ActionMode?,
+ menu: Menu?
+ ) = false
+
+ override fun onDestroyActionMode(mode: ActionMode?) {
+ onDestroyAction()
+ }
+
+ })
+}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/main/MainActivity.java b/app/src/main/java/org/kiwix/kiwixmobile/main/MainActivity.java
index ab66be396..7f3de6b2a 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/main/MainActivity.java
+++ b/app/src/main/java/org/kiwix/kiwixmobile/main/MainActivity.java
@@ -37,7 +37,6 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.CountDownTimer;
-import android.os.Environment;
import android.os.Handler;
import android.provider.Settings;
import android.text.SpannableString;
@@ -84,10 +83,10 @@ import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
+import io.reactivex.android.schedulers.AndroidSchedulers;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ -100,20 +99,18 @@ import org.kiwix.kiwixmobile.base.BaseActivity;
import org.kiwix.kiwixmobile.bookmark.BookmarkItem;
import org.kiwix.kiwixmobile.bookmark.BookmarksActivity;
import org.kiwix.kiwixmobile.data.ZimContentProvider;
-import org.kiwix.kiwixmobile.data.local.entity.Bookmark;
import org.kiwix.kiwixmobile.help.HelpActivity;
import org.kiwix.kiwixmobile.history.HistoryActivity;
import org.kiwix.kiwixmobile.history.HistoryListItem;
-import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity;
import org.kiwix.kiwixmobile.search.SearchActivity;
import org.kiwix.kiwixmobile.settings.KiwixSettingsActivity;
import org.kiwix.kiwixmobile.utils.DimenUtils;
import org.kiwix.kiwixmobile.utils.LanguageUtils;
import org.kiwix.kiwixmobile.utils.NetworkUtils;
import org.kiwix.kiwixmobile.utils.StyleUtils;
-import org.kiwix.kiwixmobile.utils.files.FileSearch;
import org.kiwix.kiwixmobile.utils.files.FileUtils;
import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity;
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.StorageObserver;
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BookOnDiskDelegate;
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskAdapter;
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem;
@@ -201,14 +198,18 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
ImageView bottomToolbarArrowBack;
@BindView(R.id.bottom_toolbar_arrow_forward)
ImageView bottomToolbarArrowForward;
- @Inject
- MainContract.Presenter presenter;
@BindView(R.id.tab_switcher_recycler_view)
RecyclerView tabRecyclerView;
@BindView(R.id.activity_main_tab_switcher)
View tabSwitcherRoot;
@BindView(R.id.tab_switcher_close_all_tabs)
FloatingActionButton closeAllTabsButton;
+
+ @Inject
+ MainContract.Presenter presenter;
+ @Inject
+ StorageObserver storageObserver;
+
private CountDownTimer hideBackToTopTimer = new CountDownTimer(1200, 1200) {
@Override
public void onTick(long millisUntilFinished) {
@@ -274,21 +275,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
closeTab(viewHolder.getAdapterPosition());
}
};
- private FileSearch fileSearch =
- new FileSearch(this, Collections.emptyList(), new FileSearch.ResultListener() {
- final List newBooks = new ArrayList<>();
- @Override public void onBookFound(BooksOnDiskListItem.BookOnDisk bookOnDisk) {
- runOnUiThread(() -> {
- newBooks.add(bookOnDisk);
- });
- }
-
- @Override
- public void onScanCompleted() {
- presenter.saveBooks(newBooks);
- }
- });
private static void updateWidgets(Context context) {
Intent intent = new Intent(context.getApplicationContext(), KiwixSearchWidget.class);
@@ -377,6 +364,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
open(bookOnDiskItem);
return Unit.INSTANCE;
},
+ null,
null),
BookOnDiskDelegate.LanguageDelegate.INSTANCE
);
@@ -739,7 +727,6 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
downloadBookButton = null;
hideBackToTopTimer.cancel();
hideBackToTopTimer = null;
- fileSearch = null;
// TODO create a base Activity class that class this.
FileUtils.deleteCachedFiles(this);
tts.shutdown();
@@ -1199,7 +1186,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
case REQUEST_READ_STORAGE_PERMISSION: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- fileSearch.scan(sharedPreferenceUtil.getPrefStorage());
+ scanStorageForZims();
} else {
Snackbar.make(drawerLayout, R.string.request_storage, Snackbar.LENGTH_LONG)
.setAction(R.string.menu_settings, view -> {
@@ -1228,6 +1215,13 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
}
}
+ private void scanStorageForZims() {
+ storageObserver.getBooksOnFileSystem()
+ .take(1)
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(presenter::saveBooks, Throwable::printStackTrace);
+ }
+
// Workaround for popup bottom menu on older devices
private void StyleMenuButtons(Menu m) {
// Find each menu item and set its text colour
@@ -2105,7 +2099,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
@Override
public void addBooks(List books) {
- booksAdapter.setItemList(books);
+ booksAdapter.setItems(books);
}
private void searchFiles() {
@@ -2116,7 +2110,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
REQUEST_READ_STORAGE_PERMISSION);
} else {
- fileSearch.scan(sharedPreferenceUtil.getPrefStorage());
+ scanStorageForZims();
}
}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/files/FileSearch.java b/app/src/main/java/org/kiwix/kiwixmobile/utils/files/FileSearch.java
deleted file mode 100644
index ae0610420..000000000
--- a/app/src/main/java/org/kiwix/kiwixmobile/utils/files/FileSearch.java
+++ /dev/null
@@ -1,296 +0,0 @@
-/*
- * Copyright 2013 Rashiq Ahmad
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
- */
-
-package org.kiwix.kiwixmobile.utils.files;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-import android.provider.MediaStore;
-import android.util.Log;
-import eu.mhutti1.utils.storage.StorageDevice;
-import eu.mhutti1.utils.storage.StorageDeviceUtils;
-import java.io.File;
-import java.io.FilenameFilter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Vector;
-import org.kiwix.kiwixmobile.data.ZimContentProvider;
-import org.kiwix.kiwixmobile.downloader.model.DownloadModel;
-import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity;
-import org.kiwix.kiwixmobile.utils.StorageUtils;
-import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem;
-
-import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX;
-
-public class FileSearch {
-
- // Array of zim file extensions
- public static final String[] zimFiles = { "zim", "zimaa" };
-
- private final Context context;
- private final List downloads;
- private final ResultListener listener;
-
- private boolean fileSystemScanCompleted = false;
- private boolean mediaStoreScanCompleted = false;
-
- public FileSearch(Context ctx, List downloads, ResultListener listener) {
- this.context = ctx;
- this.downloads = downloads;
- this.listener = listener;
- }
-
- public static synchronized LibraryNetworkEntity.Book fileToBook(String filePath) {
- LibraryNetworkEntity.Book book = null;
-
- if (ZimContentProvider.zimFileName != null) {
- ZimContentProvider.originalFileName = ZimContentProvider.zimFileName;
- }
- // Check a file isn't being opened and temporally use content provider to access details
- // This is not a great solution as we shouldn't need to fully open our ZIM files to get their metadata
- if (ZimContentProvider.canIterate) {
- if (ZimContentProvider.setZimFile(filePath) != null) {
- try {
- book = new LibraryNetworkEntity.Book();
- book.title = ZimContentProvider.getZimFileTitle();
- book.id = ZimContentProvider.getId();
- book.file = new File(filePath);
- book.size = String.valueOf(ZimContentProvider.getFileSize());
- book.favicon = ZimContentProvider.getFavicon();
- book.creator = ZimContentProvider.getCreator();
- book.publisher = ZimContentProvider.getPublisher();
- book.date = ZimContentProvider.getDate();
- book.description = ZimContentProvider.getDescription();
- book.language = ZimContentProvider.getLanguage();
- } catch (Exception e) {
- // TODO 20171215 Consider more elegant approaches.
- // This is to see if we can catch the exception at all!
- Log.e("kiwix-filesearch", "Problem parsing a book entry from the library file. ", e);
- return null;
- }
- }
- }
- // Return content provider to its previous state
- if (!ZimContentProvider.originalFileName.equals("")) {
- ZimContentProvider.setZimFile(ZimContentProvider.originalFileName);
- }
- ZimContentProvider.originalFileName = "";
-
- return book;
- }
-
- public void scan(String defaultPath) {
- // Start custom file search
- new Thread(() -> {
- scanFileSystem(defaultPath);
- fileSystemScanCompleted = true;
- checkCompleted();
- }).start();
-
- // Star mediastore search
- new Thread(() -> {
- scanMediaStore();
- mediaStoreScanCompleted = true;
- checkCompleted();
- }).start();
- }
-
- // If both searches are complete callback
- private synchronized void checkCompleted() {
- if (mediaStoreScanCompleted && fileSystemScanCompleted) {
- listener.onScanCompleted();
- }
- }
-
- public void scanMediaStore() {
- ContentResolver contentResolver = context.getContentResolver();
- Uri uri = MediaStore.Files.getContentUri("external");
-
- String[] projection = { MediaStore.MediaColumns.DATA };
- String selection =
- MediaStore.MediaColumns.DATA + " like ? or " + MediaStore.MediaColumns.DATA + " like ? ";
-
- Cursor query = contentResolver.query(uri, projection, selection,
- new String[] { "%." + zimFiles[0], "%." + zimFiles[1] }, null);
-
- if (query == null) {
- return;
- }
-
- try {
- while (query.moveToNext()) {
- File file = new File(query.getString(0));
- if (file.canRead()) {
- onFileFound(file.getAbsolutePath());
- }
- }
- } finally {
- query.close();
- }
- }
-
- // Scan through the file system and find all the files with .zim and .zimaa extensions
- public void scanFileSystem(String defaultPath) {
- FilenameFilter[] filter = new FilenameFilter[zimFiles.length];
-
- // Search all external directories that we can find.
- final ArrayList storageDevices =
- StorageDeviceUtils.getStorageDevices(context, false);
- String[] tempRoots = new String[storageDevices.size() + 2];
- int j = 0;
- tempRoots[j++] = "/mnt";
- tempRoots[j++] = defaultPath;
- for (StorageDevice storageDevice : storageDevices) {
- tempRoots[j++] = storageDevice.getName();
- }
-
- int i = 0;
- for (final String extension : zimFiles) {
- filter[i] = (dir, name) -> name.endsWith("." + extension);
- i++;
- }
-
- String dirNamePrimary = new File(
- Environment.getExternalStorageDirectory().getAbsolutePath()).toString();
-
- for (final String dirName : tempRoots) {
- if (dirNamePrimary.equals(dirName)) {
- // We already got this directory from getExternalStorageDirectory().
- continue;
- }
- File f = new File(dirName);
- if (f.isDirectory()) {
- scanDirectory(dirName, filter);
- } else {
- Log.i(TAG_KIWIX, "Skipping missing directory " + dirName);
- }
- }
- }
-
- // Iterate through the file system
- private Collection listFiles(File directory, FilenameFilter[] filter, int recurse) {
-
- Vector files = new Vector<>();
-
- File[] entries = directory.listFiles();
-
- if (entries != null) {
- for (File entry : entries) {
- for (FilenameFilter filefilter : filter) {
- if (filter == null || filefilter.accept(directory, entry.getName())) {
- files.add(entry);
- }
- }
- if ((recurse <= -1) || (recurse > 0 && entry.isDirectory())) {
- recurse--;
- files.addAll(listFiles(entry, filter, recurse));
- recurse++;
- }
- }
- }
- return files;
- }
-
- private File[] listFilesAsArray(File directory, FilenameFilter[] filter, int recurse) {
- Collection files = listFiles(directory, filter, recurse);
-
- File[] arr = new File[files.size()];
- return files.toArray(arr);
- }
-
- public static synchronized BooksOnDiskListItem.BookOnDisk fileToBookOnDisk(String filePath) {
- LibraryNetworkEntity.Book book = null;
-
- if (ZimContentProvider.zimFileName != null) {
- ZimContentProvider.originalFileName = ZimContentProvider.zimFileName;
- }
- // Check a file isn't being opened and temporally use content provider to access details
- // This is not a great solution as we shouldn't need to fully open our ZIM files to get their metadata
- if (ZimContentProvider.canIterate) {
- if (ZimContentProvider.setZimFile(filePath) != null) {
- try {
- book = new LibraryNetworkEntity.Book();
- book.title = ZimContentProvider.getZimFileTitle();
- book.id = ZimContentProvider.getId();
- book.size = String.valueOf(ZimContentProvider.getFileSize());
- book.favicon = ZimContentProvider.getFavicon();
- book.creator = ZimContentProvider.getCreator();
- book.publisher = ZimContentProvider.getPublisher();
- book.date = ZimContentProvider.getDate();
- book.description = ZimContentProvider.getDescription();
- book.language = ZimContentProvider.getLanguage();
- } catch (Exception e) {
- // TODO 20171215 Consider more elegant approaches.
- // This is to see if we can catch the exception at all!
- Log.e("kiwix-filesearch", "Problem parsing a book entry from the library file. ", e);
- return null;
- }
- }
- }
- // Return content provider to its previous state
- if (!ZimContentProvider.originalFileName.equals("")) {
- ZimContentProvider.setZimFile(ZimContentProvider.originalFileName);
- }
- ZimContentProvider.originalFileName = "";
-
- return book == null ? null
- : new BooksOnDiskListItem.BookOnDisk(null, book, new File(filePath),0L);
- }
-
- // Fill fileList with files found in the specific directory
- private void scanDirectory(String directory, FilenameFilter[] filter) {
- Log.d(TAG_KIWIX, "Searching directory " + directory);
- File[] foundFiles = listFilesAsArray(new File(directory), filter, -1);
- for (File f : foundFiles) {
- Log.d(TAG_KIWIX, "Found " + f.getAbsolutePath());
- onFileFound(f.getAbsolutePath());
- }
- }
-
- // Callback that a new file has been found
- public void onFileFound(String filePath) {
- if (fileIsDownloading(filePath)) {
- return;
- }
- BooksOnDiskListItem.BookOnDisk book = fileToBookOnDisk(filePath);
-
- if (book != null) {
- listener.onBookFound(book);
- }
- }
-
- private boolean fileIsDownloading(String filePath) {
- for (DownloadModel download : downloads) {
- if (filePath.endsWith(StorageUtils.getFileNameFromUrl(download.getBook().getUrl()))) {
- return true;
- }
- }
- return false;
- }
-
- public interface ResultListener {
- void onBookFound(BooksOnDiskListItem.BookOnDisk book);
-
- void onScanCompleted();
- }
-}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/utils/files/FileSearch.kt b/app/src/main/java/org/kiwix/kiwixmobile/utils/files/FileSearch.kt
new file mode 100644
index 000000000..25f7f56fe
--- /dev/null
+++ b/app/src/main/java/org/kiwix/kiwixmobile/utils/files/FileSearch.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 Rashiq Ahmad
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+package org.kiwix.kiwixmobile.utils.files
+
+import android.content.Context
+import android.os.Environment
+import android.provider.MediaStore.Files
+import android.provider.MediaStore.MediaColumns
+import eu.mhutti1.utils.storage.StorageDeviceUtils
+import io.reactivex.Flowable
+import io.reactivex.functions.BiFunction
+import org.kiwix.kiwixmobile.extensions.forEachRow
+import org.kiwix.kiwixmobile.extensions.get
+import java.io.File
+import javax.inject.Inject
+
+class FileSearch @Inject constructor(private val context: Context) {
+
+ val zimFileExtensions = arrayOf("zim", "zimaa")
+
+ fun scan(defaultPath: String) =
+ Flowable.combineLatest(
+ Flowable.fromCallable { scanFileSystem(defaultPath) },
+ Flowable.fromCallable(this::scanMediaStore),
+ BiFunction, List, List> { filesSystemFiles, mediaStoreFiles ->
+ filesSystemFiles + mediaStoreFiles
+ }
+ )
+
+ private fun scanMediaStore() = mutableListOf().apply {
+ queryMediaStore()
+ ?.forEachRow { cursor ->
+ File(cursor.get(MediaColumns.DATA)).takeIf(File::canRead)
+ ?.also { add(it) }
+ }
+ }
+
+ private fun queryMediaStore() = context.contentResolver
+ .query(
+ Files.getContentUri("external"),
+ arrayOf(MediaColumns.DATA),
+ MediaColumns.DATA + " like ? or " + MediaColumns.DATA + " like ? ",
+ arrayOf("%." + zimFileExtensions[0], "%." + zimFileExtensions[1]),
+ null
+ )
+
+ private fun scanFileSystem(defaultPath: String) =
+ directoryRoots(defaultPath)
+ .minus(Environment.getExternalStorageDirectory().absolutePath)
+ .fold(mutableListOf(), { acc, root ->
+ acc.apply { addAll(scanDirectory(root)) }
+ })
+
+ private fun directoryRoots(defaultPath: String) = listOf(
+ "/mnt",
+ defaultPath,
+ *StorageDeviceUtils.getStorageDevices(context, false).map { it.name }.toTypedArray()
+ )
+
+ private fun scanDirectory(directory: String) = filesMatchingExtensions(directory) ?: emptyList()
+
+ private fun filesMatchingExtensions(directory: String) = File(directory)
+ .listFiles { _, name -> name.endsWithAny(*zimFileExtensions) }
+ ?.toList()
+
+}
+
+internal fun String.endsWithAny(vararg suffixes: String) =
+ suffixes.fold(false, { acc, s -> acc or endsWith(s) })
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt
index e4ed2c862..88a77ff75 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModel.kt
@@ -52,9 +52,24 @@ import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CanWrite4G
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CannotWrite4GbFile
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.NotEnoughSpaceFor4GbFile
import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED
+import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.MultiModeFinished
+import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestDeleteMultiSelection
+import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestMultiSelection
+import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestOpen
+import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestSelect
+import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestShareMultiSelection
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.FileSelectListState
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode.MULTI
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode.NORMAL
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.StorageObserver
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.DeleteFiles
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.None
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.OpenFile
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.ShareFiles
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.SideEffect
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.StartMultiSelection
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.DividerItem
@@ -79,16 +94,26 @@ class ZimManageViewModel @Inject constructor(
private val defaultLanguageProvider: DefaultLanguageProvider,
private val dataSource: DataSource
) : ViewModel() {
+ sealed class FileSelectActions {
+ data class RequestOpen(val bookOnDisk: BookOnDisk) : FileSelectActions()
+ data class RequestSelect(val bookOnDisk: BookOnDisk) : FileSelectActions()
+ data class RequestMultiSelection(val bookOnDisk: BookOnDisk) : FileSelectActions()
+ object RequestDeleteMultiSelection : FileSelectActions()
+ object RequestShareMultiSelection : FileSelectActions()
+ object MultiModeFinished : FileSelectActions()
+ }
+ val sideEffects = PublishProcessor.create>()
val libraryItems: MutableLiveData> = MutableLiveData()
val downloadItems: MutableLiveData> = MutableLiveData()
- val bookItems: MutableLiveData> = MutableLiveData()
+ val fileSelectListStates: MutableLiveData = MutableLiveData()
val deviceListIsRefreshing = MutableLiveData()
val libraryListIsRefreshing = MutableLiveData()
val networkStates = MutableLiveData()
val languageItems = MutableLiveData>()
val requestFileSystemCheck = PublishProcessor.create()
+ val fileSelectActions = PublishProcessor.create()
val requestDownloadLibrary = BehaviorProcessor.createDefault(Unit)
val requestFiltering = BehaviorProcessor.createDefault("")
val requestLanguagesDialog = PublishProcessor.create()
@@ -127,10 +152,74 @@ class ZimManageViewModel @Inject constructor(
updateLanguagesInDao(networkLibrary, languages),
updateNetworkStates(),
updateLanguageItemsForDialog(languages),
- requestsAndConnectivtyChangesToLibraryRequests(networkLibrary)
+ requestsAndConnectivtyChangesToLibraryRequests(networkLibrary),
+ fileSelectActions()
)
}
+ private fun fileSelectActions() = fileSelectActions.subscribe({
+ sideEffects.offer(
+ when (it) {
+ is RequestOpen -> OpenFile(it.bookOnDisk)
+ is RequestMultiSelection -> startMultiSelectionAndSelectBook(it.bookOnDisk)
+ RequestDeleteMultiSelection -> DeleteFiles(selectionsFromState())
+ RequestShareMultiSelection -> ShareFiles(selectionsFromState())
+ MultiModeFinished -> noSideEffectAndClearSelectionState()
+ is RequestSelect -> noSideEffectSelectBook(it.bookOnDisk)
+ }
+ )
+ }, Throwable::printStackTrace)
+
+ private fun startMultiSelectionAndSelectBook(
+ bookOnDisk: BookOnDisk
+ ): StartMultiSelection {
+ fileSelectListStates.value?.let {
+ fileSelectListStates.postValue(
+ it.copy(
+ bookOnDiskListItems = selectBook(it, bookOnDisk),
+ selectionMode = MULTI
+ )
+ )
+ }
+ return StartMultiSelection(bookOnDisk, fileSelectActions)
+ }
+
+ private fun selectBook(
+ it: FileSelectListState,
+ bookOnDisk: BookOnDisk
+ ): List {
+ return it.bookOnDiskListItems.map { listItem ->
+ if (listItem.id == bookOnDisk.id) listItem.apply { isSelected = !isSelected }
+ else listItem
+ }
+ }
+
+ private fun noSideEffectSelectBook(bookOnDisk: BookOnDisk): SideEffect {
+ fileSelectListStates.value?.let {
+ fileSelectListStates.postValue(
+ it.copy(bookOnDiskListItems = it.bookOnDiskListItems.map { listItem ->
+ if (listItem.id == bookOnDisk.id) listItem.apply { isSelected = !isSelected }
+ else listItem
+ })
+ )
+ }
+ return None
+ }
+
+ private fun selectionsFromState() = fileSelectListStates.value?.selectedBooks ?: emptyList()
+
+ private fun noSideEffectAndClearSelectionState(): SideEffect {
+ fileSelectListStates.value?.let {
+ fileSelectListStates.postValue(
+ it.copy(
+ bookOnDiskListItems = it.bookOnDiskListItems.map { it.apply { isSelected = false } },
+ selectionMode = NORMAL
+ )
+ )
+ }
+ return None
+ }
+
private fun requestsAndConnectivtyChangesToLibraryRequests(library: PublishProcessor) =
Flowable.combineLatest(
requestDownloadLibrary,
@@ -411,10 +500,27 @@ class ZimManageViewModel @Inject constructor(
private fun updateBookItems() =
dataSource.booksOnDiskAsListItems()
.subscribe(
- bookItems::postValue,
+ { newList ->
+ fileSelectListStates.postValue(
+ fileSelectListStates.value?.let { inheritSelections(it, newList) }
+ ?: FileSelectListState(newList)
+ )
+ },
Throwable::printStackTrace
)
+ private fun inheritSelections(
+ oldState: FileSelectListState,
+ newList: MutableList
+ ): FileSelectListState {
+ return oldState.copy(
+ bookOnDiskListItems = newList.map { newBookOnDisk ->
+ val firstOrNull =
+ oldState.bookOnDiskListItems.firstOrNull { oldBookOnDisk -> oldBookOnDisk.id == newBookOnDisk.id }
+ newBookOnDisk.apply { isSelected = firstOrNull?.isSelected ?: false }
+ })
+ }
+
private fun removeCompletedDownloadsFromDb(downloadStatuses: Flowable>) =
downloadStatuses
.observeOn(Schedulers.io())
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/FileSelectListState.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/FileSelectListState.kt
new file mode 100644
index 000000000..df45efb88
--- /dev/null
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/FileSelectListState.kt
@@ -0,0 +1,37 @@
+/*
+ * Kiwix Android
+ * Copyright (C) 2018 Kiwix
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.kiwix.kiwixmobile.zim_manager.fileselect_view
+
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode.NORMAL
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
+
+data class FileSelectListState(
+ val bookOnDiskListItems: List,
+ val selectionMode: SelectionMode = NORMAL
+) {
+ val selectedBooks by lazy {
+ bookOnDiskListItems.filter { it.isSelected }.filterIsInstance(BookOnDisk::class.java)
+ }
+
+}
+
+enum class SelectionMode {
+ NORMAL,
+ MULTI
+}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserver.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserver.kt
index a33eb39f5..8552bdd61 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserver.kt
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserver.kt
@@ -1,45 +1,80 @@
package org.kiwix.kiwixmobile.zim_manager.fileselect_view
-import android.content.Context
-import android.util.Log
-import io.reactivex.processors.PublishProcessor
+import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
+import org.kiwix.kiwixmobile.data.ZimContentProvider
import org.kiwix.kiwixmobile.database.newdb.dao.NewDownloadDao
import org.kiwix.kiwixmobile.downloader.model.DownloadModel
+import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.utils.files.FileSearch
-import org.kiwix.kiwixmobile.utils.files.FileSearch.ResultListener
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
+import java.io.File
import javax.inject.Inject
class StorageObserver @Inject constructor(
- private val context: Context,
private val sharedPreferenceUtil: SharedPreferenceUtil,
- private val downloadDao: NewDownloadDao
+ downloadDao: NewDownloadDao,
+ private val fileSearch: FileSearch
) {
- private val _booksOnFileSystem = PublishProcessor.create>()
- val booksOnFileSystem = _booksOnFileSystem.distinctUntilChanged()
- .doOnSubscribe {
- downloadDao.downloads()
- .subscribeOn(Schedulers.io())
- .take(1)
- .subscribe(this::scanFiles, Throwable::printStackTrace)
+ val booksOnFileSystem = scanFiles()
+ .withLatestFrom(
+ downloadDao.downloads(),
+ BiFunction(this::toFilesThatAreNotDownloading)
+ )
+ .map {
+ it.mapNotNull { file -> convertToBookOnDisk(file) }
}
- private fun scanFiles(downloads: List) {
- FileSearch(context, downloads, object : ResultListener {
- val foundBooks = mutableSetOf()
+ private fun toFilesThatAreNotDownloading(
+ files: List,
+ downloads: List
+ ) = files.filter { fileHasNoMatchingDownload(downloads, it) }
- override fun onBookFound(book: BookOnDisk) {
- foundBooks.add(book)
- Log.i("Scanner", "File Search: Found Book " + book.book.title)
- }
+ private fun fileHasNoMatchingDownload(
+ downloads: List,
+ file: File
+ ) = downloads.firstOrNull {
+ file.absolutePath.endsWith(it.fileNameFromUrl)
+ } == null
- override fun onScanCompleted() {
- _booksOnFileSystem.onNext(foundBooks.toList())
+ private fun scanFiles() = fileSearch.scan(sharedPreferenceUtil.prefStorage)
+ .subscribeOn(Schedulers.io())
- }
- }).scan(sharedPreferenceUtil.prefStorage)
+ private fun convertToBookOnDisk(file: File): BookOnDisk? {
+ configureZimContentProvider()
+ var bookOnDisk: BookOnDisk? = null
+ if (ZimContentProvider.canIterate && ZimContentProvider.setZimFile(file.absolutePath) != null) {
+ bookOnDisk = BookOnDisk(book = bookFromZimContentProvider(), file = file)
+ }
+ resetZimContentProvider()
+ return bookOnDisk
+ }
+
+ private fun bookFromZimContentProvider() = Book().apply {
+ title = ZimContentProvider.getZimFileTitle()
+ id = ZimContentProvider.getId()
+ size = ZimContentProvider.getFileSize()
+ .toString()
+ favicon = ZimContentProvider.getFavicon()
+ creator = ZimContentProvider.getCreator()
+ publisher = ZimContentProvider.getPublisher()
+ date = ZimContentProvider.getDate()
+ description = ZimContentProvider.getDescription()
+ language = ZimContentProvider.getLanguage()
+ }
+
+ private fun configureZimContentProvider() {
+ if (ZimContentProvider.zimFileName != null) {
+ ZimContentProvider.originalFileName = ZimContentProvider.zimFileName
+ }
+ }
+
+ private fun resetZimContentProvider() {
+ if (ZimContentProvider.originalFileName != "") {
+ ZimContentProvider.setZimFile(ZimContentProvider.originalFileName)
+ }
+ ZimContentProvider.originalFileName = ""
}
}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt
index d30b9822f..854f3a55c 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/ZimFileSelectFragment.kt
@@ -21,8 +21,8 @@ package org.kiwix.kiwixmobile.zim_manager.fileselect_view
import android.Manifest
import android.content.pm.PackageManager
-import android.os.Build
import android.os.Bundle
+import android.view.ActionMode
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -32,49 +32,49 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.zim_list.file_management_no_files
import kotlinx.android.synthetic.main.zim_list.zim_swiperefresh
import kotlinx.android.synthetic.main.zim_list.zimfilelist
import org.kiwix.kiwixmobile.R
-import org.kiwix.kiwixmobile.R.string
import org.kiwix.kiwixmobile.base.BaseFragment
-import org.kiwix.kiwixmobile.data.ZimContentProvider
-import org.kiwix.kiwixmobile.database.newdb.dao.NewBookDao
import org.kiwix.kiwixmobile.di.components.ActivityComponent
import org.kiwix.kiwixmobile.extensions.toast
-import org.kiwix.kiwixmobile.utils.BookUtils
import org.kiwix.kiwixmobile.utils.Constants.REQUEST_STORAGE_PERMISSION
-import org.kiwix.kiwixmobile.utils.DialogShower
-import org.kiwix.kiwixmobile.utils.KiwixDialog.DeleteZim
import org.kiwix.kiwixmobile.utils.LanguageUtils
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
-import org.kiwix.kiwixmobile.utils.files.FileUtils
-import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel
+import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions
+import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestMultiSelection
+import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestOpen
+import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestSelect
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BookOnDiskDelegate.BookDelegate
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BookOnDiskDelegate.LanguageDelegate
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskAdapter
-import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import javax.inject.Inject
class ZimFileSelectFragment : BaseFragment() {
@Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil
- @Inject lateinit var bookDao: NewBookDao
- @Inject lateinit var dialogShower: DialogShower
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
- @Inject lateinit var bookUtils: BookUtils
+
+ private var actionMode: ActionMode? = null
+ val disposable = CompositeDisposable()
private val zimManageViewModel: ZimManageViewModel by lazy {
ViewModelProviders.of(activity!!, viewModelFactory)
.get(ZimManageViewModel::class.java)
}
+ private val bookDelegate: BookDelegate by lazy {
+ BookDelegate(sharedPreferenceUtil,
+ { offerAction(RequestOpen(it)) },
+ { offerAction(RequestMultiSelection(it)) },
+ { offerAction(RequestSelect(it)) })
+ }
+
private val booksOnDiskAdapter: BooksOnDiskAdapter by lazy {
- BooksOnDiskAdapter(
- BookDelegate(sharedPreferenceUtil, this::openOnClick, this::deleteOnLongClick),
- LanguageDelegate
- )
+ BooksOnDiskAdapter(bookDelegate, LanguageDelegate)
}
override fun inject(activityComponent: ActivityComponent) {
@@ -101,31 +101,51 @@ class ZimFileSelectFragment : BaseFragment() {
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
setHasFixedSize(true)
}
- zimManageViewModel.bookItems.observe(this, Observer {
- booksOnDiskAdapter.itemList = it!!
- checkEmpty(it)
- })
+ zimManageViewModel.fileSelectListStates.observe(this, Observer { render(it) })
+ disposable.add(sideEffects())
zimManageViewModel.deviceListIsRefreshing.observe(this, Observer {
zim_swiperefresh.isRefreshing = it!!
})
}
+ private fun sideEffects() = zimManageViewModel.sideEffects.subscribe(
+ {
+ val effectResult = it.invokeWith(activity!!)
+ if (effectResult is ActionMode) {
+ actionMode = effectResult
+ }
+ }, Throwable::printStackTrace
+ )
+
+ private fun render(state: FileSelectListState) {
+ val items = state.bookOnDiskListItems
+ bookDelegate.selectionMode = state.selectionMode
+ booksOnDiskAdapter.items = items
+ actionMode?.title = String.format("%d", state.selectedBooks.size)
+ file_management_no_files.visibility = if (items.isEmpty()) View.VISIBLE else View.GONE
+ }
+
override fun onResume() {
super.onResume()
checkPermissions()
}
- private fun checkEmpty(books: List) {
- file_management_no_files.visibility =
- if (books.isEmpty()) View.VISIBLE
- else View.GONE
+ override fun onDestroy() {
+ super.onDestroy()
+ disposable.clear()
+ }
+
+ private fun offerAction(
+ action: FileSelectActions
+ ) {
+ zimManageViewModel.fileSelectActions.offer(action)
}
private fun checkPermissions() {
if (ContextCompat.checkSelfPermission(
activity!!,
Manifest.permission.WRITE_EXTERNAL_STORAGE
- ) != PackageManager.PERMISSION_GRANTED && Build.VERSION.SDK_INT > 18
+ ) != PackageManager.PERMISSION_GRANTED
) {
context.toast(R.string.request_storage)
requestPermissions(
@@ -140,34 +160,4 @@ class ZimFileSelectFragment : BaseFragment() {
private fun requestFileSystemCheck() {
zimManageViewModel.requestFileSystemCheck.onNext(Unit)
}
-
- private fun openOnClick(it: BookOnDisk) {
- val file = it.file
- ZimContentProvider.canIterate = false
- if (!file.canRead()) {
- context.toast(string.error_filenotfound)
- } else {
- (activity as ZimManageActivity).finishResult(file.path)
- }
- }
-
- private fun deleteOnLongClick(it: BookOnDisk) {
- dialogShower.show(DeleteZim, {
- if (deleteSpecificZimFile(it)) {
- context.toast(string.delete_specific_zim_toast)
- } else {
- context.toast(string.delete_zim_failed)
- }
- })
- }
-
- private fun deleteSpecificZimFile(book: BookOnDisk): Boolean {
- val file = book.file
- FileUtils.deleteZimFile(file.path)
- if (file.exists()) {
- return false
- }
- bookDao.delete(book.databaseId!!)
- return true
- }
}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BookOnDiskDelegate.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BookOnDiskDelegate.kt
index 27e918e6e..f1a1fe3a6 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BookOnDiskDelegate.kt
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BookOnDiskDelegate.kt
@@ -18,11 +18,13 @@
package org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter
import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView.ViewHolder
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.extensions.inflate
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode.NORMAL
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BookOnDiskViewHolder.BookViewHolder
-import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BookOnDiskViewHolder.LanguageItemViewHolder
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.LanguageItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.AbsDelegateAdapter
@@ -33,17 +35,28 @@ sealed class BookOnDiskDelegate Unit,
- val longClickAction: ((BookOnDisk) -> Unit)? = null
+ val longClickAction: ((BookOnDisk) -> Unit)? = null,
+ val multiSelectAction: ((BookOnDisk) -> Unit)? = null
) : BookOnDiskDelegate() {
override val itemClass = BookOnDisk::class.java
+ var selectionMode: SelectionMode = NORMAL
+
+ override fun bind(
+ viewHolder: ViewHolder,
+ itemToBind: BooksOnDiskListItem
+ ) {
+ (viewHolder as BookOnDiskViewHolder.BookViewHolder).bind((itemToBind as BookOnDisk), selectionMode)
+ }
+
override fun createViewHolder(parent: ViewGroup) =
BookViewHolder(
parent.inflate(R.layout.item_book, false),
sharedPreferenceUtil,
clickAction,
- longClickAction
+ longClickAction,
+ multiSelectAction
)
}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BooksOnDiskAdapter.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BooksOnDiskAdapter.kt
index 28cb6997b..6cc49ca33 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BooksOnDiskAdapter.kt
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BooksOnDiskAdapter.kt
@@ -1,7 +1,6 @@
package org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.AdapterDelegate
-import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.AdapterDelegateManager
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.BaseDelegateAdapter
class BooksOnDiskAdapter(
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BooksOnDiskListItem.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BooksOnDiskListItem.kt
index 5a19b774f..bd4bb2502 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BooksOnDiskListItem.kt
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BooksOnDiskListItem.kt
@@ -6,6 +6,7 @@ import java.io.File
import java.util.Locale
sealed class BooksOnDiskListItem {
+ var isSelected: Boolean = false
abstract val id: Long
data class LanguageItem constructor(
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BooksOnDiskViewHolder.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BooksOnDiskViewHolder.kt
index fe91812d3..dd3994f1b 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BooksOnDiskViewHolder.kt
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/adapter/BooksOnDiskViewHolder.kt
@@ -3,6 +3,7 @@ package org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter
import android.graphics.ColorMatrixColorFilter
import android.view.View
import kotlinx.android.synthetic.main.header_language.header_language
+import kotlinx.android.synthetic.main.item_book.itemBookCheckbox
import kotlinx.android.synthetic.main.item_book.item_book_article_count
import kotlinx.android.synthetic.main.item_book.item_book_date
import kotlinx.android.synthetic.main.item_book.item_book_description
@@ -17,6 +18,9 @@ import org.kiwix.kiwixmobile.main.KiwixWebView
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.zim_manager.KiloByte
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.ArticleCount
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode.MULTI
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode.NORMAL
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.LanguageItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.BaseViewHolder
@@ -28,10 +32,17 @@ sealed class BookOnDiskViewHolder(containerView: View)
containerView: View,
private val sharedPreferenceUtil: SharedPreferenceUtil,
private val clickAction: (BookOnDisk) -> Unit,
- private val longClickAction: ((BookOnDisk) -> Unit)?
+ private val longClickAction: ((BookOnDisk) -> Unit)?,
+ private val multiSelectAction: ((BookOnDisk) -> Unit)?
) : BookOnDiskViewHolder(containerView) {
override fun bind(item: BookOnDisk) {
+ }
+
+ fun bind(
+ item: BookOnDisk,
+ selectionMode: SelectionMode
+ ) {
val book = item.book
item_book_title.text = book.getTitle()
item_book_date.text = book.getDate()
@@ -59,24 +70,30 @@ sealed class BookOnDiskViewHolder(containerView: View)
item_book_label_video.visibility = View.GONE
}
- containerView.setOnClickListener {
- clickAction.invoke(item)
- }
- containerView.setOnLongClickListener {
- longClickAction?.invoke(item)
- return@setOnLongClickListener true
+ itemBookCheckbox.isChecked = item.isSelected
+ when (selectionMode) {
+ MULTI -> {
+ itemBookCheckbox.visibility = View.VISIBLE
+ containerView.setOnClickListener { multiSelectAction?.invoke(item) }
+ containerView.setOnLongClickListener(null)
+ }
+ NORMAL -> {
+ itemBookCheckbox.visibility = View.GONE
+ containerView.setOnClickListener { clickAction.invoke(item) }
+ containerView.setOnLongClickListener {
+ longClickAction?.invoke(item)
+ return@setOnLongClickListener true
+ }
+ }
}
}
}
-
- class LanguageItemViewHolder(containerView: View) :
- BookOnDiskViewHolder(containerView) {
-
- override fun bind(item: LanguageItem) {
- header_language.text = item.text
- }
- }
-
}
+class LanguageItemViewHolder(containerView: View) :
+ BookOnDiskViewHolder(containerView) {
+ override fun bind(item: LanguageItem) {
+ header_language.text = item.text
+ }
+}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/DeleteFiles.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/DeleteFiles.kt
new file mode 100644
index 000000000..2e87df0ae
--- /dev/null
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/DeleteFiles.kt
@@ -0,0 +1,42 @@
+package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
+
+import android.app.Activity
+import org.kiwix.kiwixmobile.R.string
+import org.kiwix.kiwixmobile.database.newdb.dao.NewBookDao
+import org.kiwix.kiwixmobile.extensions.toast
+import org.kiwix.kiwixmobile.utils.DialogShower
+import org.kiwix.kiwixmobile.utils.KiwixDialog.DeleteZim
+import org.kiwix.kiwixmobile.utils.files.FileUtils
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
+import javax.inject.Inject
+
+class DeleteFiles(val booksOnDiskListItem: List) :
+ SideEffect {
+
+ @Inject lateinit var dialogShower: DialogShower
+ @Inject lateinit var newBookDao: NewBookDao
+
+ override fun invokeWith(activity: Activity) {
+ activityComponent(activity).inject(this)
+ booksOnDiskListItem.forEach {
+ dialogShower.show(DeleteZim, {
+ if (deleteSpecificZimFile(it)) {
+ activity.toast(string.delete_specific_zim_toast)
+ } else {
+ activity.toast(string.delete_zim_failed)
+ }
+ })
+ }
+ }
+
+ private fun deleteSpecificZimFile(book: BookOnDisk): Boolean {
+ val file = book.file
+ FileUtils.deleteZimFile(file.path)
+ if (file.exists()) {
+ return false
+ }
+ newBookDao.delete(book.databaseId!!)
+ return true
+ }
+
+}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/None.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/None.kt
new file mode 100644
index 000000000..3d38f04c3
--- /dev/null
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/None.kt
@@ -0,0 +1,8 @@
+package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
+
+import android.app.Activity
+
+object None : SideEffect {
+ override fun invokeWith(activity: Activity) {
+ }
+}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/OpenFile.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/OpenFile.kt
new file mode 100644
index 000000000..361f6ecf6
--- /dev/null
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/OpenFile.kt
@@ -0,0 +1,38 @@
+/*
+ * Kiwix Android
+ * Copyright (C) 2018 Kiwix
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
+
+import android.app.Activity
+import org.kiwix.kiwixmobile.R
+import org.kiwix.kiwixmobile.data.ZimContentProvider
+import org.kiwix.kiwixmobile.extensions.toast
+import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
+
+class OpenFile(val bookOnDisk: BookOnDisk): SideEffect {
+
+ override fun invokeWith(activity: Activity) {
+ val file = bookOnDisk.file
+ ZimContentProvider.canIterate = false
+ if (!file.canRead()) {
+ activity.toast(R.string.error_filenotfound)
+ } else {
+ (activity as ZimManageActivity).finishResult(file.path)
+ }
+ }
+}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/ShareFiles.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/ShareFiles.kt
new file mode 100644
index 000000000..9586ec5e1
--- /dev/null
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/ShareFiles.kt
@@ -0,0 +1,42 @@
+package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import androidx.core.content.FileProvider
+import org.kiwix.kiwixmobile.BuildConfig
+import org.kiwix.kiwixmobile.R
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
+
+class ShareFiles(val selectedBooks: List) : SideEffect {
+ override fun invokeWith(activity: Activity) {
+ val selectedFileShareIntent = Intent()
+ selectedFileShareIntent.action = Intent.ACTION_SEND_MULTIPLE
+ selectedFileShareIntent.type = "application/octet-stream"
+ val selectedFileContentURIs = selectedBooks.mapNotNull {
+ if (Build.VERSION.SDK_INT >= 24) {
+ FileProvider.getUriForFile(
+ activity,
+ BuildConfig.APPLICATION_ID + ".fileprovider",
+ it.file
+ )
+ } else {
+ Uri.fromFile(it.file)
+ }
+ }
+ selectedFileShareIntent.putParcelableArrayListExtra(
+ Intent.EXTRA_STREAM,
+ ArrayList(selectedFileContentURIs)
+ )
+ selectedFileShareIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ val shareChooserIntent = Intent.createChooser(
+ selectedFileShareIntent,
+ activity.getString(R.string.selected_file_cab_app_chooser_title)
+ )
+ if (shareChooserIntent.resolveActivity(activity.getPackageManager()) != null) {
+ activity.startActivity(shareChooserIntent) // Open the app chooser dialog
+ }
+ }
+
+}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/SideEffect.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/SideEffect.kt
new file mode 100644
index 000000000..113699531
--- /dev/null
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/SideEffect.kt
@@ -0,0 +1,28 @@
+/*
+ * Kiwix Android
+ * Copyright (C) 2018 Kiwix
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
+
+import android.app.Activity
+import org.kiwix.kiwixmobile.KiwixApplication
+
+interface SideEffect {
+ fun invokeWith(activity: Activity):T
+ fun activityComponent(activity: Activity) =
+ KiwixApplication.getApplicationComponent().activityComponent().activity(activity).build()
+}
+
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/StartMultiSelection.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/StartMultiSelection.kt
new file mode 100644
index 000000000..8faa86c99
--- /dev/null
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/effects/StartMultiSelection.kt
@@ -0,0 +1,27 @@
+package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
+
+import android.app.Activity
+import android.view.ActionMode
+import io.reactivex.processors.PublishProcessor
+import org.kiwix.kiwixmobile.R
+import org.kiwix.kiwixmobile.extensions.startActionMode
+import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions
+import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestDeleteMultiSelection
+import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestShareMultiSelection
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem
+
+data class StartMultiSelection(
+ val bookOnDisk: BooksOnDiskListItem.BookOnDisk,
+ val fileSelectActions: PublishProcessor
+) : SideEffect {
+ override fun invokeWith(activity: Activity) =
+ activity.startActionMode(
+ R.menu.menu_zim_files_contextual,
+ mapOf(
+ R.id.zim_file_delete_item to { fileSelectActions.offer(RequestDeleteMultiSelection) },
+ R.id.zim_file_share_item to { fileSelectActions.offer(RequestShareMultiSelection) }
+ ),
+ { fileSelectActions.offer(FileSelectActions.MultiModeFinished) }
+ )
+
+}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.kt
index f2739ff84..7b9e2edb4 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.kt
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/LibraryFragment.kt
@@ -140,7 +140,7 @@ class LibraryFragment : BaseFragment() {
}
private fun onLibraryItemsChange(it: List?) {
- libraryAdapter.itemList = it!!
+ libraryAdapter.items = it!!
if (it.isEmpty()) {
libraryErrorText.setText(
if (isNotConnected) R.string.no_network_connection
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/base/BaseDelegateAdapter.kt b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/base/BaseDelegateAdapter.kt
index f5fa281ff..40e39b9e5 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/base/BaseDelegateAdapter.kt
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/library_view/adapter/base/BaseDelegateAdapter.kt
@@ -30,7 +30,7 @@ abstract class BaseDelegateAdapter- (
setHasStableIds(true)
}
- var itemList: List
- = mutableListOf()
+ var items: List
- = mutableListOf()
set(value) {
field = value
notifyDataSetChanged()
@@ -41,19 +41,19 @@ abstract class BaseDelegateAdapter
- (
viewType: Int
) = delegateManager.createViewHolder(parent, viewType)
- override fun getItemCount() = itemList.size
+ override fun getItemCount() = items.size
override fun onBindViewHolder(
holder: ViewHolder,
position: Int
) {
- delegateManager.onBindViewHolder(itemList[position], holder)
+ delegateManager.onBindViewHolder(items[position], holder)
}
override fun getItemViewType(position: Int) =
- delegateManager.getViewTypeFor(itemList[position])
+ delegateManager.getViewTypeFor(items[position])
override fun getItemId(position: Int): Long {
- return getIdFor(itemList[position])
+ return getIdFor(items[position])
}
abstract fun getIdFor(item:ITEM):Long
diff --git a/app/src/main/res/layout/item_book.xml b/app/src/main/res/layout/item_book.xml
index 3552460d6..eb8c48d3c 100644
--- a/app/src/main/res/layout/item_book.xml
+++ b/app/src/main/res/layout/item_book.xml
@@ -4,21 +4,35 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?attr/selectableItemBackground"
+ android:background="?android:attr/selectableItemBackground"
android:paddingEnd="0dp"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:paddingStart="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
+ tools:ignore="Overdraw"
>
+
+
diff --git a/app/src/main/res/values-in b/app/src/main/res/values-in
deleted file mode 120000
index f7118b95e..000000000
--- a/app/src/main/res/values-in
+++ /dev/null
@@ -1 +0,0 @@
-values-id
\ No newline at end of file
diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-in/strings.xml
similarity index 100%
rename from app/src/main/res/values-he/strings.xml
rename to app/src/main/res/values-in/strings.xml
diff --git a/app/src/main/res/values-iw b/app/src/main/res/values-iw
deleted file mode 120000
index 57bf91954..000000000
--- a/app/src/main/res/values-iw
+++ /dev/null
@@ -1 +0,0 @@
-values-he
\ No newline at end of file
diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-iw/strings.xml
similarity index 100%
rename from app/src/main/res/values-id/strings.xml
rename to app/src/main/res/values-iw/strings.xml
diff --git a/app/src/main/res/values-ji b/app/src/main/res/values-ji
deleted file mode 120000
index 2acf80afb..000000000
--- a/app/src/main/res/values-ji
+++ /dev/null
@@ -1 +0,0 @@
-values-yi
\ No newline at end of file
diff --git a/app/src/main/res/values-yi/strings.xml b/app/src/main/res/values-ji/strings.xml
similarity index 100%
rename from app/src/main/res/values-yi/strings.xml
rename to app/src/main/res/values-ji/strings.xml
diff --git a/app/src/test/java/org/kiwix/kiwixmobile/TestModelFunctions.kt b/app/src/test/java/org/kiwix/kiwixmobile/TestModelFunctions.kt
index f2071a281..8e620aff5 100644
--- a/app/src/test/java/org/kiwix/kiwixmobile/TestModelFunctions.kt
+++ b/app/src/test/java/org/kiwix/kiwixmobile/TestModelFunctions.kt
@@ -27,11 +27,31 @@ import java.io.File
fun bookOnDisk(
book: Book = book(),
- databaseId: Long = 0L,
+ databaseId: Long? = 0L,
file: File = File("")
) = BookOnDisk(databaseId, book, file)
-fun book(id: String = "0") = Book().apply { this.id = id }
+fun book(
+ id: String = "0",
+ title: String = "",
+ size: String = "",
+ favicon: String = "",
+ creator: String = "",
+ publisher: String = "",
+ date: String = "",
+ description: String = "",
+ language: String = ""
+) = Book().apply {
+ this.id = id
+ this.title = title
+ this.size = size
+ this.favicon = favicon
+ this.creator = creator
+ this.publisher = publisher
+ this.date = date
+ this.description = description
+ this.language = language
+}
fun downloadStatus(
downloadId: Long = 0L,
diff --git a/app/src/test/java/org/kiwix/kiwixmobile/TestUtilitiyFunctions.kt b/app/src/test/java/org/kiwix/kiwixmobile/TestUtilitiyFunctions.kt
new file mode 100644
index 000000000..1f7029941
--- /dev/null
+++ b/app/src/test/java/org/kiwix/kiwixmobile/TestUtilitiyFunctions.kt
@@ -0,0 +1,34 @@
+/*
+ * Kiwix Android
+ * Copyright (C) 2018 Kiwix
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.kiwix.kiwixmobile
+
+import io.reactivex.Scheduler
+import io.reactivex.android.plugins.RxAndroidPlugins
+import io.reactivex.plugins.RxJavaPlugins
+
+fun setScheduler(replacementScheduler: Scheduler) {
+ RxJavaPlugins.setIoSchedulerHandler { scheduler -> replacementScheduler }
+ RxJavaPlugins.setComputationSchedulerHandler { scheduler -> replacementScheduler }
+ RxJavaPlugins.setNewThreadSchedulerHandler { scheduler -> replacementScheduler }
+ RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> replacementScheduler }
+}
+
+fun resetSchedulers() {
+ RxJavaPlugins.reset()
+ RxAndroidPlugins.reset()
+}
diff --git a/app/src/test/java/org/kiwix/kiwixmobile/utils/files/FileSearchTest.kt b/app/src/test/java/org/kiwix/kiwixmobile/utils/files/FileSearchTest.kt
new file mode 100644
index 000000000..a76949f85
--- /dev/null
+++ b/app/src/test/java/org/kiwix/kiwixmobile/utils/files/FileSearchTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Kiwix Android
+ * Copyright (C) 2018 Kiwix
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.kiwix.kiwixmobile.utils.files
+
+import android.content.ContentResolver
+import android.content.Context
+import android.database.Cursor
+import android.os.Environment
+import android.provider.MediaStore.MediaColumns
+import eu.mhutti1.utils.storage.StorageDevice
+import eu.mhutti1.utils.storage.StorageDeviceUtils
+import io.mockk.clearMocks
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.AfterAll
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
+import org.junit.jupiter.api.Test
+import java.io.File
+
+class FileSearchTest {
+
+ private val context: Context = mockk()
+ private lateinit var fileSearch: FileSearch
+
+ private val externalStorageDirectory: File = mockk()
+ private val contentResolver: ContentResolver = mockk()
+ private val storageDevice: StorageDevice = mockk()
+
+ private val unitTestTempDirectoryPath = "unittest${File.separator}"
+
+ @BeforeEach
+ fun init() {
+ clearMocks(context, externalStorageDirectory, contentResolver, storageDevice)
+ deleteTempDirectory()
+ mockkStatic(StorageDeviceUtils::class)
+ mockkStatic(Environment::class)
+ every { Environment.getExternalStorageDirectory() } returns externalStorageDirectory
+ every { externalStorageDirectory.absolutePath } returns "/externalStorageDirectory"
+ every { context.contentResolver } returns contentResolver
+ every { StorageDeviceUtils.getStorageDevices(context, false) } returns arrayListOf(
+ storageDevice
+ )
+ every { storageDevice.name } returns "/deviceDir"
+ fileSearch = FileSearch(context)
+ }
+
+ @AfterAll
+ fun teardown() {
+ deleteTempDirectory()
+ }
+
+ @Nested
+ inner class FileSystem {
+
+ @Test
+ fun `scan of directory that doesn't exist returns nothing`() {
+ every { contentResolver.query(any(), any(), any(), any(), any()) } returns null
+ fileSearch.scan("doesNotExist")
+ .test()
+ .assertValue(listOf())
+ }
+
+ @Test
+ fun `scan of directory that has files returns files`() {
+ val zimFile = File.createTempFile("${unitTestTempDirectoryPath}fileToFind", ".zim")
+ val zimaaFile = File.createTempFile("${unitTestTempDirectoryPath}fileToFind2", ".zimaa")
+ File.createTempFile("${unitTestTempDirectoryPath}willNotFind", ".txt")
+ every { contentResolver.query(any(), any(), any(), any(), any()) } returns null
+ val fileList = fileSearch.scan(zimFile.parent)
+ .test()
+ .values()[0]
+ assertThat(fileList).containsExactlyInAnyOrder(zimFile, zimaaFile)
+ }
+ }
+
+ @Nested
+ inner class MediaStore {
+
+ @Test
+ fun `scan media store, if files are readable they are returned`() {
+ val fileToFind = File.createTempFile("${unitTestTempDirectoryPath}fileToFind", ".zim")
+ expectFromMediaStore(fileToFind)
+ fileSearch.scan("")
+ .test()
+ .assertValue(listOf(fileToFind))
+ }
+
+ @Test
+ fun `scan media store, if files are not readable they are not returned`() {
+ val unreadableFile = File.createTempFile("${unitTestTempDirectoryPath}fileToFind", ".zim")
+ expectFromMediaStore(unreadableFile)
+ unreadableFile.delete()
+ fileSearch.scan("")
+ .test()
+ .assertValue(listOf())
+ }
+
+ private fun expectFromMediaStore(fileToFind: File) {
+ val cursor = mockk()
+ every {
+ contentResolver.query(
+ null,
+ arrayOf(MediaColumns.DATA),
+ MediaColumns.DATA + " like ? or " + MediaColumns.DATA + " like ? ",
+ arrayOf("%." + "zim", "%." + "zimaa"),
+ null
+ )
+ } returns cursor
+ every { cursor.moveToNext() } returnsMany listOf(true, false)
+ every { cursor.columnNames } returns arrayOf(MediaColumns.DATA)
+ every { cursor.getColumnIndex(MediaColumns.DATA) } returns 0
+ every { cursor.getString(0) } returns fileToFind.absolutePath
+ }
+ }
+
+ private fun deleteTempDirectory() {
+ File.createTempFile("${unitTestTempDirectoryPath}temp", ".txt")
+ .parentFile.deleteRecursively()
+ }
+}
diff --git a/app/src/test/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModelTest.kt b/app/src/test/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModelTest.kt
index 59b81cca0..75f95f0f6 100644
--- a/app/src/test/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModelTest.kt
+++ b/app/src/test/java/org/kiwix/kiwixmobile/zim_manager/ZimManageViewModelTest.kt
@@ -20,16 +20,12 @@ package org.kiwix.kiwixmobile.zim_manager
import android.app.Application
import com.jraska.livedata.test
-import io.mockk.clearMocks
+import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
-import io.reactivex.Scheduler
import io.reactivex.Single
-import io.reactivex.android.plugins.RxAndroidPlugins
-import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.processors.PublishProcessor
-import io.reactivex.schedulers.Schedulers
import io.reactivex.schedulers.TestScheduler
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeEach
@@ -55,12 +51,15 @@ import org.kiwix.kiwixmobile.downloader.model.DownloadStatus
import org.kiwix.kiwixmobile.downloader.model.UriToFileConverter
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
+import org.kiwix.kiwixmobile.resetSchedulers
+import org.kiwix.kiwixmobile.setScheduler
import org.kiwix.kiwixmobile.utils.BookUtils
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CanWrite4GbFile
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CannotWrite4GbFile
import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED
import org.kiwix.kiwixmobile.zim_manager.NetworkState.NOT_CONNECTED
+import org.kiwix.kiwixmobile.zim_manager.fileselect_view.FileSelectListState
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.StorageObserver
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
@@ -104,26 +103,14 @@ class ZimManageViewModelTest {
setScheduler(testScheduler)
}
- private fun setScheduler(replacementScheduler: Scheduler) {
- RxJavaPlugins.setIoSchedulerHandler { scheduler -> replacementScheduler }
- RxJavaPlugins.setComputationSchedulerHandler { scheduler -> replacementScheduler }
- RxJavaPlugins.setNewThreadSchedulerHandler { scheduler -> replacementScheduler }
- RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> Schedulers.trampoline() }
- }
-
@AfterAll
fun teardown() {
- RxJavaPlugins.reset()
- RxAndroidPlugins.reset()
+ resetSchedulers()
}
@BeforeEach
fun init() {
- clearMocks(
- newDownloadDao, newBookDao, newLanguagesDao, downloader,
- storageObserver, kiwixService, application, connectivityBroadcastReceiver, bookUtils,
- fat32Checker, uriToFileConverter, defaultLanguageProvider, dataSource
- )
+ clearAllMocks()
every { connectivityBroadcastReceiver.action } returns "test"
every { newDownloadDao.downloads() } returns downloads
every { newBookDao.books() } returns books
@@ -235,8 +222,8 @@ class ZimManageViewModelTest {
val expectedList = listOf(bookOnDisk())
booksOnDiskListItems.onNext(expectedList)
testScheduler.triggerActions()
- viewModel.bookItems.test()
- .assertValue(expectedList)
+ viewModel.fileSelectListStates.test()
+ .assertValue(FileSelectListState(expectedList))
}
@Test
diff --git a/app/src/test/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserverTest.kt b/app/src/test/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserverTest.kt
new file mode 100644
index 000000000..2d53c1937
--- /dev/null
+++ b/app/src/test/java/org/kiwix/kiwixmobile/zim_manager/fileselect_view/StorageObserverTest.kt
@@ -0,0 +1,149 @@
+package org.kiwix.kiwixmobile.zim_manager.fileselect_view
+
+/*
+ * Kiwix Android
+ * Copyright (C) 2018 Kiwix
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import io.mockk.clearAllMocks
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.verify
+import io.reactivex.processors.PublishProcessor
+import io.reactivex.schedulers.Schedulers
+import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.AfterAll
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.kiwix.kiwixmobile.book
+import org.kiwix.kiwixmobile.bookOnDisk
+import org.kiwix.kiwixmobile.data.ZimContentProvider
+import org.kiwix.kiwixmobile.database.newdb.dao.NewDownloadDao
+import org.kiwix.kiwixmobile.downloader.model.DownloadModel
+import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
+import org.kiwix.kiwixmobile.resetSchedulers
+import org.kiwix.kiwixmobile.setScheduler
+import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
+import org.kiwix.kiwixmobile.utils.files.FileSearch
+import java.io.File
+
+class StorageObserverTest {
+
+ private val sharedPreferenceUtil: SharedPreferenceUtil = mockk()
+ private val newDownloadDao: NewDownloadDao = mockk()
+ private val fileSearch: FileSearch = mockk()
+ private val downloadModel = mockk()
+ private val file = mockk()
+
+ private val files: PublishProcessor
> = PublishProcessor.create()
+ private val downloads: PublishProcessor> = PublishProcessor.create()
+
+ private lateinit var storageObserver: StorageObserver
+
+ init {
+ setScheduler(Schedulers.trampoline())
+ }
+
+ @AfterAll
+ fun teardown() {
+ resetSchedulers()
+ }
+
+ @BeforeEach fun init() {
+ clearAllMocks()
+ every { sharedPreferenceUtil.prefStorage } returns "a"
+ every { fileSearch.scan("a") } returns files
+ every { newDownloadDao.downloads() } returns downloads
+ storageObserver = StorageObserver(sharedPreferenceUtil, newDownloadDao, fileSearch)
+ }
+
+ @Test
+ fun `books from disk are filtered by current downloads`() {
+ withFiltering()
+ booksOnFileSystem().assertValues(listOf())
+ }
+
+ @Test
+ fun `null books from ZimContentProvider are filtered out`() {
+ withNoFiltering()
+ booksOnFileSystem().assertValues(listOf())
+ }
+
+ @Test
+ fun `iterable ZimContentProvider with zim file produces a book`() {
+ val expectedBook = book(
+ "id", "title", "1", "favicon", "creator", "publisher", "date",
+ "description", "language"
+ )
+ mockkStatic(ZimContentProvider::class)
+ withNoFiltering()
+ every { ZimContentProvider.setZimFile("This won't match") } returns ""
+ expect(expectedBook)
+ booksOnFileSystem().assertValues(
+ listOf(
+ bookOnDisk(book = expectedBook, file = file, databaseId = null)
+ )
+ )
+ assertThat(ZimContentProvider.originalFileName).isEqualTo("")
+ }
+
+ @Test
+ fun `zim provider sets zim file to original file name if it exists`() {
+ withNoFiltering()
+ mockkStatic(ZimContentProvider::class)
+ every { ZimContentProvider.setZimFile(any()) } returns null
+ ZimContentProvider.zimFileName = "myZimFileName"
+ booksOnFileSystem().assertValues(listOf())
+ verify { ZimContentProvider.setZimFile("myZimFileName") }
+ }
+
+ @Test
+ fun `zim provider does not read book if it can not iterate`() {
+ withNoFiltering()
+ ZimContentProvider.canIterate = false
+ booksOnFileSystem().assertValues(listOf())
+ }
+
+ private fun booksOnFileSystem() = storageObserver.booksOnFileSystem
+ .test()
+ .also {
+ downloads.offer(listOf(downloadModel))
+ files.offer(listOf(file))
+ }
+
+ private fun expect(expectedBook: Book) {
+ every { ZimContentProvider.getZimFileTitle() } returns expectedBook.title
+ every { ZimContentProvider.getId() } returns expectedBook.id
+ every { ZimContentProvider.getFileSize() } returns expectedBook.size.toInt()
+ every { ZimContentProvider.getFavicon() } returns expectedBook.favicon
+ every { ZimContentProvider.getCreator() } returns expectedBook.creator
+ every { ZimContentProvider.getPublisher() } returns expectedBook.publisher
+ every { ZimContentProvider.getDate() } returns expectedBook.date
+ every { ZimContentProvider.getDescription() } returns expectedBook.description
+ every { ZimContentProvider.getLanguage() } returns expectedBook.language
+ }
+
+ private fun withFiltering() {
+ every { downloadModel.fileNameFromUrl } returns "test"
+ every { file.absolutePath } returns "This is a test"
+ }
+
+ private fun withNoFiltering() {
+ every { downloadModel.fileNameFromUrl } returns "test"
+ every { file.absolutePath } returns "This won't match"
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index 06ec21335..27e70ee7f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -35,7 +35,7 @@ ext {
set("powerMockVersion", "1.6.6")
set("powerMockJUnitVersion", "1.7.4")
set("baristaVersion", "2.7.1")
- set("kotlinVersion", "1.3.31")
+ set("kotlinVersion", "1.3.40")
set("objectboxVersion", "2.3.4")
}