Merge branch 'develop' into feature/LocalFileTransfer

This commit is contained in:
Aditya-Sood 2019-07-30 19:27:24 +05:30 committed by GitHub
commit 45cfc3d579
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
108 changed files with 2019 additions and 1059 deletions

View File

@ -4,6 +4,8 @@ jdk: oraclejdk8
sudo: required
dist: trusty
env:
global:
# switch glibc to a memory conserving mode
@ -38,8 +40,6 @@ cache:
android:
components:
- tools
- platform-tools
- build-tools-28.0.3
- android-28
- extra-android-m2repository
@ -63,10 +63,11 @@ after_success:
- ./gradlew kiwixtestUploadKiwix
after_failure:
- export LOG_DIR = ${TRAVIS_HOME}/build/kiwix/kiwix-android/app/build/outputs/reports/androidTests/connected/flavors/KIWIX/
- lynx --dump ${LOG_DIR}com.android.builder.testing.ConnectedDevice.html
- lynx --dump ${LOG_DIR}com.android.builder.testing.html
- lynx --dump ${LOG_DIR}org.kiwix.kiwixmobile.tests.BasicTest.html;
- export REPORT_DIR=${TRAVIS_HOME}/build/kiwix/kiwix-android/app/build/outputs/reports/
- export LOG_DIR=${REPORT_DIR}androidTests/connected/flavors/KIWIX/
- lynx -dump ${LOG_DIR}index.html
- lynx -dump ${REPORT_DIR}lint-results-kiwixDebug.html
before_deploy:
# - export APP_CHANGELOG=$(cat app/src/kiwix/play/release-notes/en-US/default.txt)

View File

@ -46,11 +46,13 @@ Our branching strategy is based on [this article](https://nvie.com/posts/a-succe
+ **master** a history of releases, once merged to from develop and tagged we create a release on the play store & GitHub releases.
+ **develop** the actively worked on next release of the app, what we branch off of while working on new features and what we merge into upon feature completion
+ **feature/** or feature/\<username\>/ any branch under this directory is an actively developed feature, feature branches culminate in a PR, are merged and deleted. Typically a feature branch is off of develop and into develop but in rare scenarios if there is an issue in production a branch may be made off master to fix this issue, this type of feature branch must be merged to develop and master before being deleted.
+ **feature/** or feature/\<username\>/ any branch under this directory is an actively developed feature, feature branches culminate in a PR, are merged and deleted.
Typically a feature branch is off of develop and into develop but in rare scenarios if there is an issue in production a branch may be made off master to fix this issue, this type of feature branch must be merged to develop and master before being deleted.
Branch names should be in the format **#\<issue-number\>-kebab-case-title**
All branches should have distinct history and should be visually easy to follow, for this reason only preform merge commits when merging code either by PR or when synchronising.
All branches should have distinct history and should be visually easy to follow, for this reason only perform merge commits when merging code either by PR or when synchronising.
Rebasing should be avoided.
If you wish to rebase you should be following the [Golden Rule](https://www.atlassian.com/git/tutorials/merging-vs-rebasing#the-golden-rule-of-rebasing) and ahere to the advice in the heading [Aside: Rebase as cleanup is awesome in the coding lifecycle](https://www.atlassian.com/git/articles/git-team-workflows-merge-or-rebase).
### Building

View File

@ -30,10 +30,10 @@ We utilize different build variants (flavours) to build various different versio
- [Butterknife](https://jakewharton.github.io/butterknife/) - View "injection" library for Android
- [Mockito](https://github.com/mockito/mockito) - Most popular Mocking framework for unit tests written in Java
- [RxJava](https://github.com/ReactiveX/RxJava) - Reactive Extensions for the JVM a library for composing asynchronous and event-based programs using observable sequences for the Java VM.
- [ObjectBox] (https://github.com/objectbox/objectbox-java) - Reactive NoSQL Databse to replace SquiDb
- [MockK] (https://github.com/mockk/mockk) - Kotlin mocking library that allows mocking of final classes by default.
- [JUnit5] (https://github.com/junit-team/junit5/) - The next generation of JUnit
- [AssertJ] (https://github.com/joel-costigliola/assertj-core) - Fluent assertions for test code
- [ObjectBox](https://github.com/objectbox/objectbox-java) - Reactive NoSQL Databse to replace SquiDb
- [MockK](https://github.com/mockk/mockk) - Kotlin mocking library that allows mocking of final classes by default.
- [JUnit5](https://github.com/junit-team/junit5/) - The next generation of JUnit
- [AssertJ](https://github.com/joel-costigliola/assertj-core) - Fluent assertions for test code
## Contributing

View File

@ -56,9 +56,6 @@ dependencies {
archs = file("../kiwixlib/src/main/jniLibs").list()
}
// Storage Devices
implementation "eu.mhutti1.utils.storage:android-storage-devices:0.6.2"
// Android Support
implementation "androidx.appcompat:appcompat:$appCompatVersion"
implementation "com.google.android.material:material:$materialVersion"
@ -125,9 +122,8 @@ dependencies {
implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"
// Leak canary
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
androidTestImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation "android.arch.lifecycle:extensions:1.1.1"
@ -233,8 +229,11 @@ android {
//TODO stop ignoring
ignore 'MissingTranslation',
'CheckResult',
'LabelFor',
'DuplicateStrings',
'LogConditional'
warning 'UnknownNullness',
'SelectableText'
baseline file("lint-baseline.xml")
}
@ -274,6 +273,9 @@ android {
// Release Type
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
buildConfigField "String", "KIWIX_DOWNLOAD_URL", "\"http://mirror.download.kiwix.org/\""
buildConfigField "boolean", "KIWIX_ERROR_ACTIVITY", "true"
@ -408,15 +410,6 @@ android {
javaMaxHeapSize "4g"
}
/*
Add back once proguard is configured
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile("proguard-android.txt")
}
}
*/
androidExtensions {
experimental = true
}

37
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,37 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
########################
# Kiwix specific rules #
########################
#keep everything in kiwixlib
-keep class org.kiwix.kiwixlib.** { *; }
## SimpleXml
-dontwarn com.bea.xml.stream.**
-dontwarn org.simpleframework.xml.stream.**
-keep class org.simpleframework.xml.**{ *; }
-keepclassmembers,allowobfuscation class * {
@org.simpleframework.xml.* <fields>;
@org.simpleframework.xml.* <init>(...);
}

View File

@ -26,21 +26,15 @@ import com.schibsted.spain.barista.interaction.BaristaSleepInteractions;
import com.schibsted.spain.barista.rule.BaristaRule;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.kiwix.kiwixmobile.R;
import org.kiwix.kiwixmobile.intro.IntroActivity;
import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.replaceText;
import static androidx.test.espresso.action.ViewActions.swipeLeft;
import static androidx.test.espresso.action.ViewActions.swipeRight;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isChecked;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
@ -52,12 +46,11 @@ import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.kiwix.kiwixmobile.testutils.Matcher.childAtPosition;
import static org.kiwix.kiwixmobile.testutils.TestUtils.TEST_PAUSE_MS;
import static org.kiwix.kiwixmobile.testutils.ViewActions.setChecked;
public class LanguageActivityTest {
@Rule
public BaristaRule<IntroActivity> activityTestRule = BaristaRule.create(IntroActivity.class);
public BaristaRule<ZimManageActivity> activityTestRule = BaristaRule.create(ZimManageActivity.class);
@Rule
public GrantPermissionRule readPermissionRule =
GrantPermissionRule.grant(Manifest.permission.READ_EXTERNAL_STORAGE);
@ -72,46 +65,8 @@ public class LanguageActivityTest {
}
@Test
@Ignore("Broken in 2.5")//TODO: Fix in 3.0
public void testLanguageActivity() {
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
onView(withId(R.id.get_started)).perform(click());
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
// Open the Library
openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext());
onView(withText("Get Content")).perform(click());
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
ViewInteraction viewPager = onView(allOf(withId(R.id.manageViewPager),
childAtPosition(allOf(withId(R.id.zim_manager_main_activity),
childAtPosition(withId(android.R.id.content), 0)), 1),
isDisplayed()));
// Verify that the "Choose Language" and the "Search" buttons are present only in the "online" tab
onView(withContentDescription("Search")).check(matches(notNullValue()));
// Test that the language selection screen does not open if the "Choose language" button is clicked, while the data is being loaded
onView(withContentDescription("Choose a language")).check(matches(notNullValue()))
.perform(click());
viewPager.perform(swipeRight());
onView(withContentDescription("Search")).check(doesNotExist());
onView(withContentDescription("Choose a language")).check(doesNotExist());
viewPager.perform(swipeLeft());
viewPager.perform(swipeLeft());
onView(withContentDescription("Search")).check(doesNotExist());
onView(withContentDescription("Choose a language")).check(doesNotExist());
viewPager.perform(swipeRight());
// Verify that the library is still visible
onView(allOf(withText("Library"), childAtPosition(allOf(withId(R.id.toolbar),
childAtPosition(withId(R.id.toolbar_layout), 0)), 1), isDisplayed()));
// Make sure that the zim list has been loaded
//IdlingRegistry.getInstance().register(LibraryFragment.IDLING_RESOURCE);
onView(allOf(isDisplayed(), withText("Selected languages:"))).check(matches(notNullValue()));
onView(withText("Online")).perform(click());
// Open the Language Activity
onView(withContentDescription("Choose a language")).perform(click());
@ -143,18 +98,7 @@ public class LanguageActivityTest {
0),
isDisplayed()));
// Get a reference to the checkbox associated with the top unselected language
checkBox2 = onView(
allOf(withId(R.id.item_language_checkbox),
childAtPosition(
childAtPosition(
withId(R.id.recycler_view),
2),
0),
isDisplayed()));
// Initialise the language checkbox
checkBox2.perform(setChecked(false));
onView(withContentDescription("Save languages")).perform(click());
@ -164,8 +108,6 @@ public class LanguageActivityTest {
onView(withId(R.id.search_src_text)).perform(replaceText(language2), closeSoftKeyboard());
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
// Initialise the language checkbox
checkBox2.perform(setChecked(false));
onView(withContentDescription("Clear query")).perform(click());
// Collapse the search view to go to the full list of languages
onView(withContentDescription("Collapse")).perform(click());
@ -178,7 +120,7 @@ public class LanguageActivityTest {
onView(withId(R.id.search_src_text)).perform(replaceText(language1), closeSoftKeyboard());
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
checkBox2.perform(click());
checkBox1.perform(click());
onView(withContentDescription("Clear query")).perform(click());
// Collapse the search view to go to the full list of languages
onView(withContentDescription("Collapse")).perform(click());
@ -187,7 +129,7 @@ public class LanguageActivityTest {
onView(withId(R.id.search_src_text)).perform(replaceText(language2), closeSoftKeyboard());
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
checkBox2.perform(click());
checkBox1.perform(click());
onView(withContentDescription("Clear query")).perform(click());
onView(withContentDescription("Collapse")).perform(click());
@ -216,7 +158,7 @@ public class LanguageActivityTest {
onView(withId(R.id.search_src_text)).perform(replaceText(language1), closeSoftKeyboard());
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
checkBox2.check(matches(not(isChecked())));
checkBox1.check(matches(not(isChecked())));
onView(withContentDescription("Clear query")).perform(click());
onView(withContentDescription("Collapse")).perform(click());
@ -224,7 +166,7 @@ public class LanguageActivityTest {
onView(withId(R.id.search_src_text)).perform(replaceText(language2), closeSoftKeyboard());
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
checkBox2.check(matches(not(isChecked())));
checkBox1.check(matches(not(isChecked())));
onView(withContentDescription("Clear query")).perform(click());
onView(withContentDescription("Collapse")).perform(click());
onView(withContentDescription("Navigate up")).perform(click());
@ -237,7 +179,7 @@ public class LanguageActivityTest {
onView(withId(R.id.search_src_text)).perform(replaceText(language1), closeSoftKeyboard());
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
checkBox2.perform(click());
checkBox1.perform(click());
onView(withContentDescription("Clear query")).perform(click());
onView(withContentDescription("Collapse")).perform(click());
@ -245,7 +187,7 @@ public class LanguageActivityTest {
onView(withId(R.id.search_src_text)).perform(replaceText(language2), closeSoftKeyboard());
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
checkBox2.perform(click());
checkBox1.perform(click());
onView(withContentDescription("Clear query")).perform(click());
onView(withContentDescription("Collapse")).perform(click());
onView(withContentDescription("Save languages")).perform(click());

View File

@ -49,10 +49,6 @@ public class SettingsTest {
withKey("pref_hidetoolbar")))
.perform(click());
onData(allOf(
is(instanceOf(Preference.class)),
withKey("pref_bottomtoolbar")))
.perform(click());
onData(allOf(
is(instanceOf(Preference.class)),

View File

@ -0,0 +1,59 @@
/*
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
*
* 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 eu.mhutti1.utils.storage;
import android.annotation.SuppressLint;
class ExternalPaths {
@SuppressLint("SdCardPath") private static final String[] paths = {
"/storage/sdcard0",
"/storage/sdcard1",
"/storage/extsdcard",
"/storage/extSdCard",
"/storage/sdcard0/external_sdcard",
"/mnt/sdcard/external_sd",
"/mnt/external_sd",
"/mnt/media_rw/*",
"/removable/microsd",
"/mnt/emmc",
"/storage/external_SD",
"/storage/ext_sd",
"/storage/removable/sdcard1",
"/data/sdext",
"/data/sdext2",
"/data/sdext3",
"/data/sdext2",
"/data/sdext3",
"/data/sdext4",
"/sdcard",
"/sdcard1",
"/sdcard2",
"/storage/microsd",
"/mnt/extsd",
"/extsd",
"/mnt/sdcard",
"/misc/android",
};
public static String[] getPossiblePaths() {
return paths;
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
*
* 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 eu.mhutti1.utils.storage;
import android.os.Build;
import android.os.StatFs;
import android.util.Log;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
public class StorageDevice {
// File object containing device path
private final File mFile;
private final boolean mInternal;
private boolean mDuplicate = true;
public StorageDevice(String path, boolean internal) {
mFile = new File(path);
mInternal = internal;
if (mFile.exists()) {
createLocationCode();
}
}
public StorageDevice(File file, boolean internal) {
mFile = file;
mInternal = internal;
if (mFile.exists()) {
createLocationCode();
}
}
// Get device path
public String getName() {
return mFile.getPath();
}
// Get available space on device
public String getSize() {
return bytesToHuman(getAvailableBytes());
}
private Long getAvailableBytes() {
StatFs statFs = new StatFs(mFile.getPath());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return statFs.getBlockSizeLong() * statFs.getAvailableBlocksLong();
} else {
return (long) statFs.getBlockSize() * (long) statFs.getAvailableBlocks();
}
}
public String getTotalSize() {
return bytesToHuman(getTotalBytes());
}
// Get total space on device
private Long getTotalBytes() {
StatFs statFs = new StatFs((mFile.getPath()));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return statFs.getBlockSizeLong() * statFs.getBlockCountLong();
} else {
return (long) statFs.getBlockSize() * (long) statFs.getBlockCount();
}
}
// Convert bytes to human readable form
private static String bytesToHuman(long size) {
long Kb = 1 * 1024;
long Mb = Kb * 1024;
long Gb = Mb * 1024;
long Tb = Gb * 1024;
long Pb = Tb * 1024;
long Eb = Pb * 1024;
if (size < Kb) return floatForm(size) + " byte";
if (size >= Kb && size < Mb) return floatForm((double) size / Kb) + " KB";
if (size >= Mb && size < Gb) return floatForm((double) size / Mb) + " MB";
if (size >= Gb && size < Tb) return floatForm((double) size / Gb) + " GB";
if (size >= Tb && size < Pb) return floatForm((double) size / Tb) + " TB";
if (size >= Pb && size < Eb) return floatForm((double) size / Pb) + " PB";
if (size >= Eb) return floatForm((double) size / Eb) + " EB";
return "???";
}
public boolean isInternal() {
return mInternal;
}
public File getPath() {
return mFile;
}
private static String floatForm(double d) {
return new DecimalFormat("#.#").format(d);
}
// Create unique file to identify duplicate devices.
private void createLocationCode() {
if (!getLocationCodeFromFolder(mFile)) {
File locationCode = new File(mFile.getPath(), ".storageLocationMarker");
try {
locationCode.createNewFile();
FileWriter fw = new FileWriter(locationCode);
fw.write(mFile.getPath());
fw.close();
} catch (IOException e) {
Log.d("android-storage-devices", "Unable to create marker file, duplicates may be listed");
}
}
}
// Check if there is already a device code in our path
private boolean getLocationCodeFromFolder(File folder) {
File locationCode = new File(folder.getPath(), ".storageLocationMarker");
if (locationCode.exists()) {
try ( BufferedReader br = new BufferedReader(new FileReader(locationCode))){
if (br.readLine().equals(mFile.getPath())) {
mDuplicate = false;
} else {
mDuplicate = true;
return true;
}
} catch (Exception e) {
return true;
}
}
String path = folder.getPath();
String parent = path.substring(0, path.lastIndexOf("/"));
if (parent.equals("")) {
mDuplicate = false;
return false;
}
return getLocationCodeFromFolder(new File(parent));
}
public boolean isDuplicate() {
return mDuplicate;
}
}

View File

@ -0,0 +1,131 @@
/*
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
*
* 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 eu.mhutti1.utils.storage;
import android.content.Context;
import android.os.Environment;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList;
public class StorageDeviceUtils {
public static ArrayList<StorageDevice> getStorageDevices(Context context, boolean writable) {
ArrayList<StorageDevice> storageDevices = new ArrayList<>();
// Add as many possible mount points as we know about
// Only add this device if its very likely that we have missed a users sd card
if (Environment.isExternalStorageEmulated()) {
// This is our internal storage directory
storageDevices.add(new StorageDevice(
generalisePath(Environment.getExternalStorageDirectory().getPath(), writable), true));
} else {
// This is an external storage directory
storageDevices.add(new StorageDevice(
generalisePath(Environment.getExternalStorageDirectory().getPath(), writable), false));
}
// These are possible manufacturer sdcard mount points
String[] paths = ExternalPaths.getPossiblePaths();
for (String path : paths) {
if (path.endsWith("*")) {
File root = new File(path.substring(0, path.length() - 1));
File[] directories = root.listFiles(file -> file.isDirectory());
if (directories != null) {
for (File dir : directories) {
storageDevices.add(new StorageDevice(dir, false));
}
}
} else {
storageDevices.add(new StorageDevice(path, false));
}
}
// Iterate through any sdcards manufacturers may have specified
for (File file : ContextCompat.getExternalFilesDirs(context, "")) {
if (file != null) {
storageDevices.add(new StorageDevice(generalisePath(file.getPath(), writable), false));
}
}
// Check all devices exist, we can write to them if required and they are not duplicates
return checkStorageValid(writable, storageDevices);
}
// Remove app specific path from directories so that we can search them from the top
private static String generalisePath(String path, boolean writable) {
if (writable) {
return path;
}
int endIndex = path.lastIndexOf("/Android/data/");
if (endIndex != -1) {
return path.substring(0, endIndex);
}
return path;
}
private static ArrayList<StorageDevice> checkStorageValid(boolean writable,
ArrayList<StorageDevice> storageDevices) {
ArrayList<StorageDevice> activeDevices = new ArrayList<>();
ArrayList<StorageDevice> devicePaths = new ArrayList<>();
for (StorageDevice device : storageDevices) {
if (existsAndIsDirAndWritableIfRequiredAndNotDuplicate(writable, devicePaths, device)) {
activeDevices.add(device);
devicePaths.add(device);
}
}
return activeDevices;
}
private static boolean existsAndIsDirAndWritableIfRequiredAndNotDuplicate(boolean writable,
ArrayList<StorageDevice> devicePaths, StorageDevice device) {
final File devicePath = device.getPath();
return devicePath.exists()
&& devicePath.isDirectory()
&& (canWrite(devicePath) || !writable)
&& !device.isDuplicate()
&& !devicePaths.contains(device);
}
// Amazingly file.canWrite() does not always return the correct value
private static boolean canWrite(File file) {
final String filePath = file + "/test.txt";
try {
RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "rw");
FileChannel fileChannel = randomAccessFile.getChannel();
FileLock fileLock = fileChannel.lock();
fileLock.release();
fileChannel.close();
randomAccessFile.close();
return true;
} catch (Exception ex) {
return false;
} finally {
new File(filePath).delete();
}
}
}

View File

@ -0,0 +1,72 @@
/*
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
*
* 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 eu.mhutti1.utils.storage;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.ArrayList;
import org.kiwix.kiwixmobile.R;
class StorageSelectArrayAdapter extends ArrayAdapter<StorageDevice> {
private final String mInternal;
private final String mExternal;
public StorageSelectArrayAdapter(Context context, int resource, ArrayList<StorageDevice> devices,
String internal, String external) {
super(context, resource, devices);
mInternal = internal;
mExternal = external;
}
@SuppressLint("SetTextI18n") @Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = View.inflate(getContext(), R.layout.device_item, null);
holder = new ViewHolder();
holder.fileName = convertView.findViewById(R.id.file_name);
holder.fileSize = convertView.findViewById(R.id.file_size);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
StorageDevice device = getItem(position);
if (device.isInternal()) {
holder.fileName.setText(mInternal);
} else {
holder.fileName.setText(mExternal);
}
holder.fileSize.setText(device.getSize() + " / " + device.getTotalSize());
return convertView;
}
class ViewHolder {
TextView fileName;
TextView fileSize;
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 2016 Isaac Hutt <mhutti1@gmail.com>
*
* 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 eu.mhutti1.utils.storage;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import java.io.File;
import org.kiwix.kiwixmobile.R;
public class StorageSelectDialog extends DialogFragment implements ListView.OnItemClickListener {
// Activities/Fragments can create instances of a StorageSelectDialog and bind a listener to get its result
public static final String STORAGE_DIALOG_THEME = "THEME";
public static final String STORAGE_DIALOG_INTERNAL = "INTERNAL";
public static final String STORAGE_DIALOG_EXTERNAL = "EXTERNAL";
private StorageSelectArrayAdapter mAdapter;
private OnSelectListener mOnSelectListener;
private String mTitle;
private String mInternal = "Internal";
private String mExternal = "External";
@Override
public void onCreate(Bundle savedInstanceState) {
if (getArguments() != null) {
// Set string values
mInternal = getArguments().getString(STORAGE_DIALOG_INTERNAL, mInternal);
mExternal = getArguments().getString(STORAGE_DIALOG_EXTERNAL, mExternal);
// Set the theme to a supplied value
if (getArguments().containsKey(STORAGE_DIALOG_THEME)) {
setStyle(DialogFragment.STYLE_NORMAL, getArguments().getInt(STORAGE_DIALOG_THEME));
}
}
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.storage_select_dialog, container, false);
TextView title = rootView.findViewById(R.id.title);
title.setText(mTitle);
ListView listView = rootView.findViewById(R.id.device_list);
mAdapter = new StorageSelectArrayAdapter(getActivity(), 0,
StorageDeviceUtils.getStorageDevices(getActivity(), true), mInternal, mExternal);
listView.setAdapter(mAdapter);
listView.setOnItemClickListener(this);
Button button = rootView.findViewById(R.id.button);
final EditText editText = rootView.findViewById(R.id.editText);
button.setOnClickListener(view -> {
if (editText.getText().length() != 0) {
String path = editText.getText().toString();
if (new File(path).exists()) {
mAdapter.add(new StorageDevice(path, false));
}
}
});
return rootView;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (mOnSelectListener != null) {
mOnSelectListener.selectionCallback(mAdapter.getItem(position));
}
dismiss();
}
public void setOnSelectListener(OnSelectListener selectListener) {
mOnSelectListener = selectListener;
}
public interface OnSelectListener {
void selectionCallback(StorageDevice s);
}
@Override
public void show(FragmentManager fm, String text) {
mTitle = text;
super.show(fm, text);
}
}

View File

@ -26,7 +26,6 @@ import android.util.Log;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.multidex.MultiDexApplication;
import com.jakewharton.threetenabp.AndroidThreeTen;
import com.squareup.leakcanary.LeakCanary;
import dagger.android.AndroidInjector;
import dagger.android.DispatchingAndroidInjector;
import dagger.android.HasActivityInjector;
@ -73,11 +72,6 @@ public class KiwixApplication extends MultiDexApplication implements HasActivity
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
AndroidThreeTen.init(this);
if (isExternalStorageWritable()) {
File appDirectory = new File(Environment.getExternalStorageDirectory() + "/Kiwix");
@ -110,7 +104,6 @@ public class KiwixApplication extends MultiDexApplication implements HasActivity
Log.d("KIWIX", "Started KiwixApplication");
applicationComponent.inject(this);
LeakCanary.install(this);
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(buildThreadPolicy(new StrictMode.ThreadPolicy.Builder()));
StrictMode.setVmPolicy(buildVmPolicy(new StrictMode.VmPolicy.Builder()));

View File

@ -92,7 +92,7 @@ public class BookmarksActivity extends BaseActivity implements BookmarksContract
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter.attachView(this);
setContentView(R.layout.activity_bookmarks_history_language);
setContentView(R.layout.activity_bookmarks);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();

View File

@ -24,7 +24,7 @@ data class LanguageEntity(
)
fun toLanguageModel() =
Language(locale, active, occurencesOfLanguage)
Language(locale, active, occurencesOfLanguage, id)
}
class StringToLocaleConverter : PropertyConverter<Locale, String> {

View File

@ -12,7 +12,6 @@ import org.kiwix.kiwixmobile.history.HistoryModule;
import org.kiwix.kiwixmobile.intro.IntroActivity;
import org.kiwix.kiwixmobile.intro.IntroModule;
import org.kiwix.kiwixmobile.language.LanguageActivity;
import org.kiwix.kiwixmobile.language.LanguageModule;
import org.kiwix.kiwixmobile.main.MainActivity;
import org.kiwix.kiwixmobile.main.MainModule;
import org.kiwix.kiwixmobile.search.SearchActivity;
@ -61,7 +60,7 @@ public abstract class ActivityBindingModule {
public abstract SplashActivity provideSplashActivity();
@PerActivity
@ContributesAndroidInjector(modules = LanguageModule.class)
@ContributesAndroidInjector
public abstract LanguageActivity provideLanguageActivity();
@PerActivity

View File

@ -36,7 +36,7 @@ import org.kiwix.kiwixmobile.data.remote.UserAgentInterceptor;
@Provides @Singleton OkHttpClient provideOkHttpClient() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(BuildConfig.DEBUG ? Level.BODY : Level.BASIC);
logging.setLevel(BuildConfig.DEBUG ? Level.BASIC : Level.NONE);
return new OkHttpClient().newBuilder().followRedirects(true).followSslRedirects(true)
.connectTimeout(10, TimeUnit.SECONDS)

View File

@ -7,6 +7,7 @@ import dagger.Module
import dagger.multibindings.IntoMap
import org.kiwix.kiwixmobile.KiwixViewModelFactory
import org.kiwix.kiwixmobile.di.ViewModelKey
import org.kiwix.kiwixmobile.language.viewmodel.LanguageViewModel
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel
/*
@ -31,7 +32,12 @@ abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(ZimManageViewModel::class)
internal abstract fun bindUserViewModel(userViewModel: ZimManageViewModel): ViewModel
internal abstract fun bindZimManageViewModel(zimManageViewModel: ZimManageViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(LanguageViewModel::class)
internal abstract fun bindLanguageViewModel(languageViewModel: LanguageViewModel): ViewModel
@Binds
internal abstract fun bindViewModelFactory(factory: KiwixViewModelFactory): ViewModelProvider.Factory

View File

@ -23,7 +23,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.layout_download_management.download_management_no_downloads
@ -31,6 +30,7 @@ import kotlinx.android.synthetic.main.layout_download_management.zim_downloader_
import org.kiwix.kiwixmobile.base.BaseFragment
import org.kiwix.kiwixmobile.di.components.ActivityComponent
import org.kiwix.kiwixmobile.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.extensions.viewModel
import org.kiwix.kiwixmobile.utils.DialogShower
import org.kiwix.kiwixmobile.utils.KiwixDialog.YesNoDialog.StopDownload
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
@ -44,10 +44,10 @@ class DownloadFragment : BaseFragment() {
@Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil
@Inject lateinit var downloader: Downloader
private val zimManageViewModel: ZimManageViewModel by lazy {
ViewModelProviders.of(activity!!, viewModelFactory)
.get(ZimManageViewModel::class.java)
private val zimManageViewModel by lazy {
activity!!.viewModel<ZimManageViewModel>(viewModelFactory)
}
private val downloadAdapter = DownloadAdapter {
dialogShower.show(StopDownload, { downloader.cancelDownload(it) })
}

View File

@ -1,10 +1,16 @@
package org.kiwix.kiwixmobile.extensions
import android.app.Activity
import android.content.Intent
import android.view.ActionMode
import android.view.ActionMode.Callback
import android.view.Menu
import android.view.MenuItem
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import org.kiwix.kiwixmobile.base.BaseActivity
fun Activity.startActionMode(
menuId: Int,
@ -41,3 +47,12 @@ fun Activity.startActionMode(
})
}
inline fun <reified T : Activity> Activity.start() {
startActivity(Intent(this, T::class.java))
}
inline fun <reified T : ViewModel> FragmentActivity.viewModel(viewModelFactory: ViewModelProvider.Factory) =
ViewModelProviders.of(this, viewModelFactory)
.get(T::class.java)

View File

@ -106,7 +106,7 @@ public class HistoryActivity extends BaseActivity implements HistoryContract.Vie
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter.attachView(this);
setContentView(R.layout.activity_bookmarks_history_language);
setContentView(R.layout.activity_history);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();

View File

@ -1,111 +0,0 @@
package org.kiwix.kiwixmobile.language;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.kiwix.kiwixmobile.R;
import org.kiwix.kiwixmobile.base.BaseActivity;
import org.kiwix.kiwixmobile.zim_manager.Language;
public class LanguageActivity extends BaseActivity implements LanguageContract.View {
public static final String LANGUAGE_LIST = "languages";
private final ArrayList<Language> languages = new ArrayList<>();
private final ArrayList<Language> allLanguages = new ArrayList<>();
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.recycler_view)
RecyclerView recyclerView;
@Inject
LanguageContract.Presenter presenter;
private LanguageAdapter languageAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter.attachView(this);
setContentView(R.layout.activity_bookmarks_history_language);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.drawable.ic_clear_white_24dp);
actionBar.setTitle(R.string.select_languages);
}
languages.addAll(getIntent().getParcelableArrayListExtra(LANGUAGE_LIST));
allLanguages.addAll(languages);
languageAdapter = new LanguageAdapter(languages);
recyclerView.setAdapter(languageAdapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_language, menu);
MenuItem search = menu.findItem(R.id.menu_language_search);
((SearchView) search.getActionView()).setOnQueryTextListener(
new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
languages.clear();
languages.addAll(allLanguages);
presenter.filerLanguages(languages, newText);
return true;
}
});
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
case R.id.menu_language_save:
languages.clear();
languages.addAll(allLanguages);
presenter.saveLanguages(languages);
Toast.makeText(this, getString(R.string.languages_saved), Toast.LENGTH_SHORT).show();
Intent intent = new Intent();
intent.putParcelableArrayListExtra(LANGUAGE_LIST, languages);
setResult(RESULT_OK, intent);
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
presenter.detachView();
super.onDestroy();
}
@Override
public void notifyLanguagesFiltered(List<Language> languages) {
this.languages.clear();
this.languages.addAll(languages);
languageAdapter.categorizeLanguages();
languageAdapter.notifyDataSetChanged();
}
}

View File

@ -0,0 +1,110 @@
package org.kiwix.kiwixmobile.language
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.widget.SearchView
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.activity_language.language_progressbar
import kotlinx.android.synthetic.main.activity_language.recycler_view
import kotlinx.android.synthetic.main.activity_language.toolbar
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.base.BaseActivity
import org.kiwix.kiwixmobile.extensions.viewModel
import org.kiwix.kiwixmobile.language.viewmodel.Action.Select
import org.kiwix.kiwixmobile.language.viewmodel.State.Content
import org.kiwix.kiwixmobile.language.viewmodel.State.Loading
import org.kiwix.kiwixmobile.language.viewmodel.State.Saving
import org.kiwix.kiwixmobile.language.adapter.LanguageAdapter
import org.kiwix.kiwixmobile.language.adapter.LanguageDelegate.HeaderDelegate
import org.kiwix.kiwixmobile.language.adapter.LanguageDelegate.LanguageItemDelegate
import org.kiwix.kiwixmobile.language.viewmodel.Action
import org.kiwix.kiwixmobile.language.viewmodel.LanguageViewModel
import org.kiwix.kiwixmobile.language.viewmodel.State
import org.kiwix.kiwixmobile.zim_manager.SimpleTextListener
import javax.inject.Inject
class LanguageActivity : BaseActivity() {
private val languageViewModel by lazy { viewModel<LanguageViewModel>(viewModelFactory) }
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
private val compositeDisposable = CompositeDisposable()
private val languageAdapter =
LanguageAdapter(
LanguageItemDelegate { languageViewModel.actions.offer(Select(it)) },
HeaderDelegate()
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_language)
setSupportActionBar(toolbar)
supportActionBar?.let {
it.setDisplayHomeAsUpEnabled(true)
it.setHomeAsUpIndicator(R.drawable.ic_clear_white_24dp)
it.setTitle(R.string.select_languages)
}
recycler_view.run {
adapter = languageAdapter
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
setHasFixedSize(true)
}
languageViewModel.state.observe(this, Observer {
render(it)
})
compositeDisposable.add(
languageViewModel.effects.subscribe(
{
it.invokeWith(this)
},
Throwable::printStackTrace
)
)
}
override fun onDestroy() {
super.onDestroy()
compositeDisposable.clear()
}
private fun render(state: State) = when (state) {
Loading -> language_progressbar.show()
is Content -> {
language_progressbar.hide()
languageAdapter.items = state.viewItems
}
Saving -> Unit
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_language, menu)
val search = menu.findItem(R.id.menu_language_search)
(search.actionView as SearchView).setOnQueryTextListener(SimpleTextListener {
languageViewModel.actions.offer(Action.Filter(it))
})
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
onBackPressed()
return true
}
R.id.menu_language_save -> {
languageViewModel.actions.offer(Action.SaveAll)
return true
}
}
return super.onOptionsItemSelected(item)
}
}

View File

@ -1,139 +0,0 @@
package org.kiwix.kiwixmobile.language;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import java.util.ArrayList;
import org.kiwix.kiwixmobile.R;
import org.kiwix.kiwixmobile.zim_manager.Language;
class LanguageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
private final ArrayList<Language> languages;
private final ArrayList<Language> selectedLanguages = new ArrayList<>();
private final ArrayList<Language> unselectedLanguages = new ArrayList<>();
LanguageAdapter(ArrayList<Language> languages) {
this.languages = languages;
categorizeLanguages();
}
void categorizeLanguages() {
selectedLanguages.clear();
unselectedLanguages.clear();
//for (Language language : languages) {
// if (language.active != null && language.active.equals(true)) {
// selectedLanguages.add(language);
// } else {
// language.active = false;
// unselectedLanguages.add(language);
// }
//}
//Collections.sort(selectedLanguages);
//Collections.sort(unselectedLanguages);
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == TYPE_ITEM) {
View view =
LayoutInflater.from(parent.getContext()).inflate(R.layout.item_language, parent, false);
return new ViewHolder(view);
}
return new Header(
LayoutInflater.from(parent.getContext()).inflate(R.layout.header_date, parent, false));
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder item, int position) {
if (item instanceof Header) {
Header header = (Header) item;
if (position == 0) {
header.header.setText(R.string.your_languages);
} else {
header.header.setText(R.string.other_languages);
}
return;
}
Language language;
if (position - 1 < selectedLanguages.size()) {
language = selectedLanguages.get(position - 1);
} else {
language = unselectedLanguages.get(position - selectedLanguages.size() - 2);
}
ViewHolder holder = (ViewHolder) item;
//holder.languageName.setText(language.language);
//holder.languageLocalizedName.setText(language.languageLocalized);
//holder.booksCount.setText(holder.booksCount.getContext().getResources()
// .getQuantityString(R.plurals.books_count, language.booksCount, language.booksCount));
//if (language.active == null) {
// language.active = false;
//}
//holder.checkBox.setChecked(language.active);
//View.OnClickListener onClickListener = v -> {
// language.active = holder.checkBox.isChecked();
// if (language.active) {
// unselectedLanguages.remove(language);
// selectedLanguages.add(language);
// } else {
// unselectedLanguages.add(language);
// selectedLanguages.remove(language);
// }
// Collections.sort(selectedLanguages);
// Collections.sort(unselectedLanguages);
// notifyDataSetChanged();
//};
//holder.itemView.setOnClickListener(v -> {
// holder.checkBox.toggle();
// onClickListener.onClick(v);
//});
//holder.checkBox.setOnClickListener(onClickListener);
}
@Override
public int getItemViewType(int position) {
if (position == 0 || position == selectedLanguages.size() + 1) {
return TYPE_HEADER;
}
return TYPE_ITEM;
}
@Override
public int getItemCount() {
return selectedLanguages.size() + unselectedLanguages.size() + 2;
}
class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.item_language_name)
TextView languageName;
@BindView(R.id.item_language_localized_name)
TextView languageLocalizedName;
@BindView(R.id.item_language_books_count)
TextView booksCount;
@BindView(R.id.item_language_checkbox)
CheckBox checkBox;
ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
class Header extends RecyclerView.ViewHolder {
@BindView(R.id.header_date)
TextView header;
Header(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
}

View File

@ -1,17 +0,0 @@
package org.kiwix.kiwixmobile.language;
import java.util.List;
import org.kiwix.kiwixmobile.base.BaseContract;
import org.kiwix.kiwixmobile.zim_manager.Language;
interface LanguageContract {
interface View extends BaseContract.View<Presenter> {
void notifyLanguagesFiltered(List<Language> languages);
}
interface Presenter extends BaseContract.Presenter<View> {
void filerLanguages(List<Language> languages, String query);
void saveLanguages(List<Language> languages);
}
}

View File

@ -1,14 +0,0 @@
package org.kiwix.kiwixmobile.language;
import dagger.Module;
import dagger.Provides;
import org.kiwix.kiwixmobile.di.PerActivity;
@Module
public class LanguageModule {
@PerActivity
@Provides
LanguageContract.Presenter provideLanguagePresenter(LanguagePresenter presenter) {
return presenter;
}
}

View File

@ -1,76 +0,0 @@
package org.kiwix.kiwixmobile.language;
import android.util.Log;
import io.reactivex.CompletableObserver;
import io.reactivex.Scheduler;
import io.reactivex.disposables.Disposable;
import java.util.List;
import javax.inject.Inject;
import org.kiwix.kiwixmobile.base.BasePresenter;
import org.kiwix.kiwixmobile.data.DataSource;
import org.kiwix.kiwixmobile.di.PerActivity;
import org.kiwix.kiwixmobile.di.qualifiers.Computation;
import org.kiwix.kiwixmobile.di.qualifiers.MainThread;
import org.kiwix.kiwixmobile.zim_manager.Language;
@PerActivity
class LanguagePresenter extends BasePresenter<LanguageContract.View>
implements LanguageContract.Presenter {
private final Scheduler mainThread;
private final Scheduler computation;
private final DataSource dataSource;
@Inject LanguagePresenter(DataSource dataSource, @Computation Scheduler computation,
@MainThread Scheduler mainThread) {
this.computation = computation;
this.mainThread = mainThread;
this.dataSource = dataSource;
}
@Override
public void filerLanguages(List<Language> languages, String query) {
//Observable.fromIterable(languages)
// .filter(language -> language.language.toLowerCase().contains(query.toLowerCase()) ||
// language.languageLocalized.toLowerCase().contains(query.toLowerCase()))
// .toList()
// .subscribeOn(computation)
// .observeOn(mainThread)
// .subscribe(new SingleObserver<List<Language>>() {
// @Override
// public void onSubscribe(Disposable d) {
// compositeDisposable.add(d);
// }
//
// @Override
// public void onSuccess(List<Language> languages) {
// view.notifyLanguagesFiltered(languages);
// }
//
// @Override
// public void onError(Throwable e) {
// Log.e("LanguagePresenter", e.toString());
// }
// });
}
@Override
public void saveLanguages(List<Language> languages) {
dataSource.saveLanguages(languages)
.subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
Log.e("LanguagePresenter", e.toString());
}
});
}
}

View File

@ -0,0 +1,27 @@
/*
* Kiwix Android
* Copyright (C) 2018 Kiwix <android.kiwix.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.kiwix.kiwixmobile.language.adapter
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.AdapterDelegate
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.BaseDelegateAdapter
class LanguageAdapter(
vararg delegates: AdapterDelegate<LanguageListItem>
) : BaseDelegateAdapter<LanguageListItem>(*delegates) {
override fun getIdFor(item: LanguageListItem) = item.id
}

View File

@ -0,0 +1,30 @@
package org.kiwix.kiwixmobile.language.adapter
import android.view.ViewGroup
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.extensions.inflate
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.HeaderItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListViewHolder.HeaderViewHolder
import org.kiwix.kiwixmobile.language.adapter.LanguageListViewHolder.LanguageViewHolder
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.AbsDelegateAdapter
sealed class LanguageDelegate<I : LanguageListItem, VH : LanguageListViewHolder<I>> :
AbsDelegateAdapter<I, LanguageListItem, VH> {
class HeaderDelegate : LanguageDelegate<HeaderItem, HeaderViewHolder>() {
override val itemClass = HeaderItem::class.java
override fun createViewHolder(parent: ViewGroup) =
HeaderViewHolder(parent.inflate(R.layout.header_date, false))
}
class LanguageItemDelegate(private val clickAction: (LanguageItem) -> Unit) : LanguageDelegate<LanguageItem, LanguageViewHolder>() {
override val itemClass = LanguageItem::class.java
override fun createViewHolder(parent: ViewGroup) =
LanguageViewHolder(parent.inflate(R.layout.item_language, false), clickAction)
}
}

View File

@ -0,0 +1,22 @@
package org.kiwix.kiwixmobile.language.adapter
import org.kiwix.kiwixmobile.zim_manager.Language
sealed class LanguageListItem {
abstract val id: Long
data class HeaderItem constructor(
override val id: Long
) : LanguageListItem() {
companion object {
const val SELECTED = Long.MAX_VALUE
const val OTHER = Long.MIN_VALUE
}
}
data class LanguageItem(
val language: Language,
override val id: Long = language.id
) : LanguageListItem()
}

View File

@ -0,0 +1,42 @@
package org.kiwix.kiwixmobile.language.adapter
import android.view.View
import kotlinx.android.synthetic.main.header_date.header_date
import kotlinx.android.synthetic.main.item_language.item_language_books_count
import kotlinx.android.synthetic.main.item_language.item_language_checkbox
import kotlinx.android.synthetic.main.item_language.item_language_clickable_area
import kotlinx.android.synthetic.main.item_language.item_language_localized_name
import kotlinx.android.synthetic.main.item_language.item_language_name
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.HeaderItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.BaseViewHolder
sealed class LanguageListViewHolder<T : LanguageListItem>(override val containerView: View) :
BaseViewHolder<T>(containerView) {
class HeaderViewHolder(view: View) : LanguageListViewHolder<HeaderItem>(view) {
override fun bind(item: HeaderItem) {
header_date.setText(
if (item.id == HeaderItem.SELECTED) R.string.your_languages
else R.string.other_languages
)
}
}
class LanguageViewHolder(
view: View,
val clickAction: (LanguageItem) -> Unit
) : LanguageListViewHolder<LanguageItem>(view) {
override fun bind(item: LanguageItem) {
val language = item.language
item_language_name.text = language.language
item_language_localized_name.text = language.languageLocalized
item_language_books_count.text = containerView.resources.getQuantityString(
R.plurals.books_count, language.occurencesOfLanguage, language.occurencesOfLanguage
)
item_language_checkbox.isChecked = language.active
item_language_clickable_area.setOnClickListener { clickAction(item) }
}
}
}

View File

@ -0,0 +1,11 @@
package org.kiwix.kiwixmobile.language.viewmodel
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.zim_manager.Language
sealed class Action {
data class UpdateLanguages(val languages: List<Language>) : Action()
data class Filter(val filter: String) : Action()
data class Select(val language: LanguageItem) : Action()
object SaveAll : Action()
}

View File

@ -0,0 +1,91 @@
package org.kiwix.kiwixmobile.language.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.database.newdb.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.language.viewmodel.Action.SaveAll
import org.kiwix.kiwixmobile.language.viewmodel.Action.Select
import org.kiwix.kiwixmobile.language.viewmodel.Action.UpdateLanguages
import org.kiwix.kiwixmobile.language.viewmodel.State.Content
import org.kiwix.kiwixmobile.language.viewmodel.State.Loading
import org.kiwix.kiwixmobile.language.viewmodel.State.Saving
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.SideEffect
import javax.inject.Inject
class LanguageViewModel @Inject constructor(
private val languageDao: NewLanguagesDao
) : ViewModel() {
val state = MutableLiveData<State>().apply { value = Loading }
val actions = PublishProcessor.create<Action>()
val effects = PublishProcessor.create<SideEffect<*>>()
private val compositeDisposable = CompositeDisposable()
init {
compositeDisposable.addAll(
actions.map { reduce(it, state.value!!) }
.distinctUntilChanged()
.subscribe(state::postValue, Throwable::printStackTrace),
languageDao.languages().filter { it.isNotEmpty() }
.subscribe(
{
actions.offer(UpdateLanguages(it))
},
Throwable::printStackTrace
)
)
}
private fun reduce(
action: Action,
currentState: State
): State {
return when (action) {
is UpdateLanguages -> when (currentState) {
Loading -> Content(action.languages)
else -> currentState
}
is Filter -> {
when (currentState) {
is Content -> filterContent(action.filter, currentState)
else -> currentState
}
}
is Select ->
when (currentState) {
is Content -> updateSelection(action.language, currentState)
else -> currentState
}
SaveAll ->
when (currentState) {
is Content -> saveAll(currentState)
else -> currentState
}
}
}
private fun saveAll(currentState: Content): State {
effects.offer(
SaveLanguagesAndFinish(
currentState.items, languageDao
)
)
return Saving
}
private fun updateSelection(
languageItem: LanguageItem,
currentState: Content
) = currentState.select(languageItem)
private fun filterContent(
filter: String,
currentState: Content
) = currentState.updateFilter(filter)
}

View File

@ -0,0 +1,39 @@
/*
* Kiwix Android
* Copyright (C) 2018 Kiwix <android.kiwix.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.kiwix.kiwixmobile.language.viewmodel
import android.app.Activity
import io.reactivex.Flowable
import io.reactivex.schedulers.Schedulers
import org.kiwix.kiwixmobile.database.newdb.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.zim_manager.Language
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.SideEffect
data class SaveLanguagesAndFinish(
val languages: List<Language>,
val languageDao: NewLanguagesDao
) : SideEffect<Unit> {
override fun invokeWith(activity: Activity) {
Flowable.fromCallable { languageDao.insert(languages) }
.subscribeOn(Schedulers.io())
.subscribe({
activity.finish()
}, Throwable::printStackTrace)
}
}

View File

@ -0,0 +1,63 @@
package org.kiwix.kiwixmobile.language.viewmodel
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.HeaderItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.zim_manager.Language
sealed class State {
object Loading : State()
object Saving : State()
data class Content(
val items: List<Language>,
val filter: String = "",
val viewItems: List<LanguageListItem> = createViewList(
items, filter
)
) : State() {
fun select(languageItem: LanguageItem) = Content(
items.map { if (it.id == languageItem.id) it.copy(active = !it.active) else it },
filter
)
fun updateFilter(filter: String) = Content(items, filter)
companion object {
internal fun createViewList(
items: List<Language>,
filter: String
) = activeItems(
items, filter
) + otherItems(items, filter)
private fun activeItems(
items: List<Language>,
filter: String
) =
createLanguageSection(
items, filter, { it.active }, HeaderItem.SELECTED
)
private fun otherItems(
items: List<Language>,
filter: String
) =
createLanguageSection(
items, filter, { !it.active }, HeaderItem.OTHER
)
private fun createLanguageSection(
items: List<Language>,
filter: String,
filterCondition: (Language) -> Boolean,
headerId: Long
) = items.filter(filterCondition)
.filter { filter.isEmpty() or it.matches(filter) }
.takeIf { it.isNotEmpty() }
?.let { listOf(HeaderItem(headerId)) + it.map { language -> LanguageItem(language) } }
?: emptyList()
}
}
}

View File

@ -39,7 +39,6 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
@ -49,13 +48,14 @@ import static org.kiwix.kiwixmobile.utils.Constants.NOTES_DIRECTORY;
/**
* Created by @author Aditya-Sood (21/05/19) as a part of GSoC 2019
*
* AddNoteDialog extends DialogFragment and is used to display the note corresponding to a particular
* article (of a particular zim file/wiki/book) as a full-screen dialog fragment.
* AddNoteDialog extends DialogFragment and is used to display the note corresponding to a
* particular article (of a particular zim file/wiki/book) as a full-screen dialog fragment.
*
* Notes are saved as text files at location: "{External Storage}/Kiwix/Notes/ZimFileName/ArticleUrl.txt"
* */
*/
public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDialogFragment.UserClickListener {
public class AddNoteDialog extends DialogFragment
implements ConfirmationAlertDialogFragment.UserClickListener {
public static final String TAG = "AddNoteDialog";
@ -70,36 +70,51 @@ public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDi
private Unbinder unbinder;
private String zimFileName;
private String zimFileTitle;
private String articleTitle;
private String zimNoteDirectoryName; // Corresponds to "ZimFileName" of "{External Storage}/Kiwix/Notes/ZimFileName/ArticleUrl.txt"
private String articleNotefileName; // Corresponds to "ArticleUrl" of "{External Storage}/Kiwix/Notes/ZimFileName/ArticleUrl.txt"
// Corresponds to "ZimFileName" of "{External Storage}/Kiwix/Notes/ZimFileName/ArticleUrl.txt"
private String zimNoteDirectoryName;
// Corresponds to "ArticleUrl" of "{External Storage}/Kiwix/Notes/ZimFileName/ArticleUrl.txt"
private String articleNotefileName;
private boolean noteFileExists = false;
private boolean noteEdited = false; // Keeps track of state of the note (whether edited since last save)
boolean noteEdited = false; // Keeps track of state of the note (whether edited since last save)
private String ZIM_NOTES_DIRECTORY; // Stores path to directory for the currently open zim's notes
private String ZIM_NOTES_DIRECTORY; // Stores path to directory for the currently open zim's notes
public AddNoteDialog(SharedPreferenceUtil sharedPreferenceUtil) {
public AddNoteDialog(@NonNull SharedPreferenceUtil sharedPreferenceUtil) {
this.sharedPreferenceUtil = sharedPreferenceUtil;
}
@Override
public void onCreate(Bundle savedInstanceState) {
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, sharedPreferenceUtil.nightMode() ? R.style.AddNoteDialogStyle_Night : R.style.AddNoteDialogStyle);
setStyle(DialogFragment.STYLE_NORMAL,
sharedPreferenceUtil.nightMode() ? R.style.AddNoteDialogStyle_Night
: R.style.AddNoteDialogStyle);
zimFileTitle = ZimContentProvider.getZimFileTitle();
articleTitle = ((MainActivity)getActivity()).getCurrentWebView().getTitle();
// Returns name of the form ".../Kiwix/granbluefantasy_en_all_all_nopic_2018-10.zim"
zimFileName = ZimContentProvider.getZimFile();
zimNoteDirectoryName = getZimNoteDirectoryName();
articleNotefileName = getArticleNotefileName();
if (zimFileName != null) { // No zim file currently opened
zimFileTitle = ZimContentProvider.getZimFileTitle();
articleTitle = ((MainActivity) getActivity()).getCurrentWebView().getTitle();
ZIM_NOTES_DIRECTORY = NOTES_DIRECTORY + zimNoteDirectoryName + "/";
zimNoteDirectoryName = getZimNoteDirectoryName();
articleNotefileName = getArticleNotefileName();
ZIM_NOTES_DIRECTORY = NOTES_DIRECTORY + zimNoteDirectoryName + "/";
} else {
showToast(R.string.error_filenotfound, Toast.LENGTH_LONG);
closeKeyboard();
getFragmentManager().beginTransaction().remove(AddNoteDialog.this).commit();
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public @NonNull View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.dialog_add_note, container, false);
unbinder = ButterKnife.bind(this, view);
@ -142,7 +157,8 @@ public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDi
addNoteEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
@ -152,26 +168,26 @@ public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDi
}
@Override
public void afterTextChanged(Editable s) {}
public void afterTextChanged(Editable s) {
}
});
return view;
}
private @NonNull String getZimNoteDirectoryName() {
String zimFileName = ZimContentProvider.getZimFile(); // Returns name of the form ".../Kiwix/granbluefantasy_en_all_all_nopic_2018-10.zim"
String noteDirectoryName = getTextAfterLastSlashWithoutExtension(zimFileName);
return (!noteDirectoryName.isEmpty()) ? noteDirectoryName : zimFileTitle; // Incase the required ZIM file name couldn't be extracted
return (!noteDirectoryName.isEmpty()) ? noteDirectoryName : zimFileTitle;
}
private @NonNull String getArticleNotefileName() {
String articleUrl = ((MainActivity) getActivity()).getCurrentWebView().getUrl(); // Returns url of the form: "content://org.kiwix.kiwixmobile.zim.base/A/Main_Page.html"
// Returns url of the form: "content://org.kiwix.kiwixmobile.zim.base/A/Main_Page.html"
String articleUrl = ((MainActivity) getActivity()).getCurrentWebView().getUrl();
String notefileName = getTextAfterLastSlashWithoutExtension(articleUrl);
return (!notefileName.isEmpty()) ? notefileName : articleTitle; // Incase the required html file name couldn't be extracted
return (!notefileName.isEmpty()) ? notefileName : articleTitle;
}
private @NonNull String getTextAfterLastSlashWithoutExtension(@NonNull String path) {
@ -185,8 +201,8 @@ public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDi
int rightmostSlash = path.lastIndexOf('/');
int rightmostDot = path.lastIndexOf('.');
if(rightmostSlash > -1 && rightmostDot > -1) {
return (path.substring(rightmostSlash+1, rightmostDot));
if (rightmostSlash > -1 && rightmostDot > -1) {
return (path.substring(rightmostSlash + 1, rightmostDot));
}
return ""; // If couldn't find the dot and/or slash
@ -204,16 +220,18 @@ public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDi
};
}
private void exitAddNoteDialog() {
if(noteEdited) {
Fragment previousInstance = getActivity().getSupportFragmentManager().findFragmentByTag(ConfirmationAlertDialogFragment.TAG);
void exitAddNoteDialog() {
if (noteEdited) {
Fragment previousInstance = getActivity().getSupportFragmentManager()
.findFragmentByTag(ConfirmationAlertDialogFragment.TAG);
if(previousInstance == null) {
if (previousInstance == null) {
// Custom AlertDialog for taking user confirmation before closing note dialog in case of unsaved changes
DialogFragment newFragment = new ConfirmationAlertDialogFragment(sharedPreferenceUtil, TAG, R.string.confirmation_alert_dialog_message);
newFragment.show(getActivity().getSupportFragmentManager(), ConfirmationAlertDialogFragment.TAG);
DialogFragment newFragment = new ConfirmationAlertDialogFragment(sharedPreferenceUtil, TAG,
R.string.confirmation_alert_dialog_message);
newFragment.show(getActivity().getSupportFragmentManager(),
ConfirmationAlertDialogFragment.TAG);
}
} else {
// Closing unedited note dialog straightaway
dismissAddNoteDialog();
@ -221,36 +239,33 @@ public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDi
}
private void disableMenuItems() {
if(toolbar.getMenu() != null) {
if (toolbar.getMenu() != null) {
MenuItem saveItem = toolbar.getMenu().findItem(R.id.save_note);
MenuItem shareItem = toolbar.getMenu().findItem(R.id.share_note);
saveItem.setEnabled(false);
shareItem.setEnabled(false);
saveItem.getIcon().setAlpha(130);
shareItem.getIcon().setAlpha(130);
} else {
Log.d(TAG, "Toolbar without inflated menu");
}
}
private void enableSaveNoteMenuItem() {
if(toolbar.getMenu() != null) {
void enableSaveNoteMenuItem() {
if (toolbar.getMenu() != null) {
MenuItem saveItem = toolbar.getMenu().findItem(R.id.save_note);
saveItem.setEnabled(true);
saveItem.getIcon().setAlpha(255);
} else {
Log.d(TAG, "Toolbar without inflated menu");
}
}
private void enableShareNoteMenuItem() {
if(toolbar.getMenu() != null) {
void enableShareNoteMenuItem() {
if (toolbar.getMenu() != null) {
MenuItem shareItem = toolbar.getMenu().findItem(R.id.share_note);
shareItem.setEnabled(true);
shareItem.getIcon().setAlpha(255);
} else {
Log.d(TAG, "Toolbar without inflated menu");
}
@ -260,32 +275,35 @@ public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDi
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if(!noteFileExists) {
if (!noteFileExists) {
// Prepare for input in case of empty/new note
addNoteEditText.requestFocus();
showKeyboard();
}
}
public void showKeyboard(){
InputMethodManager inputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
private void showKeyboard() {
InputMethodManager inputMethodManager =
(InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
public void closeKeyboard(){
InputMethodManager inputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
void closeKeyboard() {
InputMethodManager inputMethodManager =
(InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
}
private void saveNote(String noteText) {
void saveNote(String noteText) {
/* String content of the EditText, given by noteText, is saved into the text file given by:
* "{External Storage}/Kiwix/Notes/ZimFileTitle/ArticleTitle.txt"
* */
if(isExternalStorageWritable()) {
if (isExternalStorageWritable()) {
if(ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (ContextCompat.checkSelfPermission(getContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "WRITE_EXTERNAL_STORAGE permission not granted");
showToast(R.string.note_save_unsuccessful, Toast.LENGTH_LONG);
return;
@ -294,12 +312,12 @@ public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDi
File notesFolder = new File(ZIM_NOTES_DIRECTORY);
boolean folderExists = true;
if(!notesFolder.exists()) {
if (!notesFolder.exists()) {
// Try creating folder if it doesn't exist
folderExists = notesFolder.mkdirs();
}
if(folderExists) {
if (folderExists) {
File noteFile = new File(notesFolder.getAbsolutePath(), articleNotefileName + ".txt");
// Save note text-file code:
@ -309,21 +327,17 @@ public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDi
fileOutputStream.close();
showToast(R.string.note_save_successful, Toast.LENGTH_SHORT);
noteEdited = false; // As no unsaved changes remain
} catch (IOException e) {
e.printStackTrace();
showToast(R.string.note_save_unsuccessful, Toast.LENGTH_LONG);
}
} else {
showToast(R.string.note_save_unsuccessful, Toast.LENGTH_LONG);
Log.d(TAG, "Required folder doesn't exist");
}
}
else {
} else {
showToast(R.string.note_save_error_storage_not_writable, Toast.LENGTH_LONG);
}
}
private void displayNote() {
@ -335,30 +349,20 @@ public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDi
File noteFile = new File(ZIM_NOTES_DIRECTORY + articleNotefileName + ".txt");
if(noteFile.exists()) {
if (noteFile.exists()) {
noteFileExists = true;
StringBuilder contents = new StringBuilder();
try {
try (BufferedReader input = new BufferedReader(new java.io.FileReader(noteFile))) {
String line = null;
BufferedReader input = new BufferedReader(new java.io.FileReader(noteFile));
try {
String line = null;
while((line = input.readLine()) != null) {
contents.append(line);
contents.append(System.getProperty("line.separator"));
}
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "Error reading line with BufferedReader");
} finally {
input.close();
while ((line = input.readLine()) != null) {
contents.append(line);
contents.append(System.getProperty("line.separator"));
}
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "Error closing BufferedReader");
Log.d(TAG, "Error reading line with BufferedReader");
}
addNoteEditText.setText(contents.toString()); // Display the note content
@ -369,49 +373,51 @@ public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDi
// No action in case the note file for the currently open article doesn't exist
}
private void shareNote() {
void shareNote() {
/* The note text file corresponding to the currently open article, given at:
* "{External Storage}/Kiwix/Notes/ZimFileTitle/ArticleTitle.txt"
* is shared via an app-chooser intent
* */
if(noteEdited) {
saveNote(addNoteEditText.getText().toString()); // Save edited note before sharing the text file
if (noteEdited) {
saveNote(
addNoteEditText.getText().toString()); // Save edited note before sharing the text file
}
File noteFile = new File(ZIM_NOTES_DIRECTORY + articleNotefileName + ".txt");
Uri noteFileUri = null;
if(noteFile.exists()) {
if (noteFile.exists()) {
if (Build.VERSION.SDK_INT >= 24) {
// From Nougat 7 (API 24) access to files is shared temporarily with other apps
// Need to use FileProvider for the same
noteFileUri = FileProvider.getUriForFile(getContext(), BuildConfig.APPLICATION_ID+".fileprovider", noteFile);
noteFileUri =
FileProvider.getUriForFile(getContext(), BuildConfig.APPLICATION_ID + ".fileprovider",
noteFile);
} else {
noteFileUri = Uri.fromFile(noteFile);
}
} else {
showToast(R.string.note_share_error_file_missing, Toast.LENGTH_SHORT);
}
if(noteFileUri != null) {
if (noteFileUri != null) {
Intent noteFileShareIntent = new Intent(Intent.ACTION_SEND);
noteFileShareIntent.setType("application/octet-stream");
noteFileShareIntent.putExtra(Intent.EXTRA_STREAM, noteFileUri);
noteFileShareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Intent shareChooser = Intent.createChooser(noteFileShareIntent, getString(R.string.note_share_app_chooser_title));
Intent shareChooser = Intent.createChooser(noteFileShareIntent,
getString(R.string.note_share_app_chooser_title));
if(noteFileShareIntent.resolveActivity(getActivity().getPackageManager()) != null) {
if (noteFileShareIntent.resolveActivity(getActivity().getPackageManager()) != null) {
startActivity(shareChooser);
}
}
}
public static boolean isExternalStorageWritable() {
static boolean isExternalStorageWritable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
@ -440,7 +446,7 @@ public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDi
super.onStart();
Dialog dialog = getDialog();
if(dialog != null) {
if (dialog != null) {
int width = ViewGroup.LayoutParams.MATCH_PARENT;
int height = ViewGroup.LayoutParams.MATCH_PARENT;
dialog.getWindow().setLayout(width, height);
@ -454,5 +460,4 @@ public class AddNoteDialog extends DialogFragment implements ConfirmationAlertDi
unbinder.unbind();
}
}
}

View File

@ -20,6 +20,7 @@
package org.kiwix.kiwixmobile.main;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.ActivityNotFoundException;
@ -154,7 +155,7 @@ import static org.kiwix.kiwixmobile.utils.StyleUtils.dialogStyle;
import static org.kiwix.kiwixmobile.utils.UpdateUtils.reformatProviderUrl;
public class MainActivity extends BaseActivity implements WebViewCallback,
MainContract.View{
MainContract.View {
private static final String NEW_TAB = "NEW_TAB";
private static final String HOME_URL = "file:///android_asset/home.html";
@ -276,7 +277,6 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
}
};
private static void updateWidgets(Context context) {
Intent intent = new Intent(context.getApplicationContext(), KiwixSearchWidget.class);
intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
@ -321,6 +321,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -333,6 +334,15 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
setSupportActionBar(toolbar);
actionBar = getSupportActionBar();
toolbar.setOnTouchListener(new OnSwipeTouchListener(this) {
@Override
@SuppressLint("SyntheticAccessor")
public void onSwipeBottom() {
showTabSwitcher();
}
});
tableDrawerRight =
tableDrawerRightContainer.getHeaderView(0).findViewById(R.id.right_drawer_list);
@ -359,13 +369,13 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
wasHideToolbar = isHideToolbar;
booksAdapter = new BooksOnDiskAdapter(
new BookOnDiskDelegate.BookDelegate(sharedPreferenceUtil,
bookOnDiskItem -> {
open(bookOnDiskItem);
return Unit.INSTANCE;
},
null,
null),
new BookOnDiskDelegate.BookDelegate(sharedPreferenceUtil,
bookOnDiskItem -> {
open(bookOnDiskItem);
return Unit.INSTANCE;
},
null,
null),
BookOnDiskDelegate.LanguageDelegate.INSTANCE
);
@ -846,14 +856,14 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
break;
case R.id.menu_add_note:
if(requestExternalStorageWritePermissionForNotes()) {
if (requestExternalStorageWritePermissionForNotes()) {
// Check permission since notes are stored in the public-external storage
showAddNoteDialog();
}
break;
case R.id.menu_clear_notes:
if(requestExternalStorageWritePermissionForNotes()) { // Check permission since notes are stored in the public-external storage
if (requestExternalStorageWritePermissionForNotes()) { // Check permission since notes are stored in the public-external storage
showClearAllNotesDialog();
}
break;
@ -939,12 +949,13 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
}
/** Method to delete all user notes */
private void clearAllNotes() {
void clearAllNotes() {
boolean result = true; // Result of all delete() calls is &&-ed to this variable
if(AddNoteDialog.isExternalStorageWritable()) {
if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (AddNoteDialog.isExternalStorageWritable()) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
Log.d("MainActivity", "WRITE_EXTERNAL_STORAGE permission not granted");
showToast(R.string.ext_storage_permission_not_granted, Toast.LENGTH_LONG);
return;
@ -955,17 +966,17 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
File notesDirectory = new File(NOTES_DIRECTORY);
File[] filesInNotesDirectory = notesDirectory.listFiles();
if(filesInNotesDirectory == null) { // Notes folder doesn't exist
if (filesInNotesDirectory == null) { // Notes folder doesn't exist
showToast(R.string.notes_deletion_none_found, Toast.LENGTH_LONG);
return;
}
for(File wikiFileDirectory : filesInNotesDirectory) {
if(wikiFileDirectory.isDirectory()) {
for (File wikiFileDirectory : filesInNotesDirectory) {
if (wikiFileDirectory.isDirectory()) {
File[] filesInWikiDirectory = wikiFileDirectory.listFiles();
for(File noteFile : filesInWikiDirectory) {
if(noteFile.isFile()) {
for (File noteFile : filesInWikiDirectory) {
if (noteFile.isFile()) {
result = result && noteFile.delete();
}
}
@ -974,10 +985,11 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
result = result && wikiFileDirectory.delete(); // Wiki specific notes directory deleted
}
result = result && notesDirectory.delete(); // "{External Storage}/Kiwix/Notes" directory deleted
result =
result && notesDirectory.delete(); // "{External Storage}/Kiwix/Notes" directory deleted
}
if(result) {
if (result) {
showToast(R.string.notes_deletion_successful, Toast.LENGTH_SHORT);
} else {
showToast(R.string.notes_deletion_unsuccessful, Toast.LENGTH_SHORT);
@ -990,23 +1002,24 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
Fragment previousInstance = getSupportFragmentManager().findFragmentByTag(AddNoteDialog.TAG);
// To prevent multiple instances of the DialogFragment
if(previousInstance == null) {
if (previousInstance == null) {
/* Since the DialogFragment is never added to the back-stack, so findFragmentByTag()
* returning null means that the AddNoteDialog is currently not on display (as doesn't exist)
**/
* returning null means that the AddNoteDialog is currently not on display (as doesn't exist)
**/
AddNoteDialog dialogFragment = new AddNoteDialog(sharedPreferenceUtil);
dialogFragment.show(fragmentTransaction, AddNoteDialog.TAG); // For DialogFragments, show() handles the fragment commit and display
dialogFragment.show(fragmentTransaction, AddNoteDialog.TAG);
// For DialogFragments, show() handles the fragment commit and display
}
}
private boolean requestExternalStorageWritePermissionForNotes() {
if(Build.VERSION.SDK_INT >= 23) { // For Marshmallow & higher API levels
if (Build.VERSION.SDK_INT >= 23) { // For Marshmallow & higher API levels
if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
return true;
} else {
if(shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
/* shouldShowRequestPermissionRationale() returns false when:
* 1) User has previously checked on "Don't ask me again", and/or
* 2) Permission has been disabled on device
@ -1014,9 +1027,9 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
showToast(R.string.ext_storage_permission_rationale_add_note, Toast.LENGTH_LONG);
}
requestPermissions(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_STORAGE_PERMISSION_ADD_NOTE);
requestPermissions(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE },
REQUEST_WRITE_STORAGE_PERMISSION_ADD_NOTE);
}
} else { // For Android versions below Marshmallow 6.0 (API 23)
return true; // As already requested at install time
}
@ -1202,12 +1215,12 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
case REQUEST_WRITE_STORAGE_PERMISSION_ADD_NOTE: {
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Successfully granted permission, so opening the note keeper
showAddNoteDialog();
} else {
Toast.makeText(getApplicationContext(), getString(R.string.ext_storage_write_permission_denied_add_note), Toast.LENGTH_LONG);
Toast.makeText(getApplicationContext(),
getString(R.string.ext_storage_write_permission_denied_add_note), Toast.LENGTH_LONG);
}
break;
@ -1327,7 +1340,8 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
//Check maybe need refresh
String articleUrl = getCurrentWebView().getUrl();
boolean isBookmark = false;
BookmarkItem bookmark = BookmarkItem.fromZimContentProvider(getCurrentWebView().getTitle(),articleUrl);
BookmarkItem bookmark =
BookmarkItem.fromZimContentProvider(getCurrentWebView().getTitle(), articleUrl);
if (articleUrl != null && !bookmarks.contains(articleUrl)) {
if (ZimContentProvider.getId() != null) {
presenter.saveBookmark(bookmark);
@ -1442,12 +1456,11 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
private void updateBottomToolbarVisibility() {
if (checkNull(bottomToolbar)) {
if (sharedPreferenceUtil.getPrefBottomToolbar() && !HOME_URL.equals(
if (!HOME_URL.equals(
getCurrentWebView().getUrl())
&& tabSwitcherRoot.getVisibility() != View.VISIBLE) {
bottomToolbar.setVisibility(View.VISIBLE);
if (getCurrentWebView() instanceof ToolbarStaticKiwixWebView
&& sharedPreferenceUtil.getPrefBottomToolbar()) {
if (getCurrentWebView() instanceof ToolbarStaticKiwixWebView) {
contentFrame.setPadding(0, 0, 0,
(int) getResources().getDimension(R.dimen.bottom_toolbar_height));
} else {
@ -1979,7 +1992,8 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
String url = getCurrentWebView().getUrl();
if (url != null && !url.equals(HOME_URL)) {
final long timeStamp = System.currentTimeMillis();
SimpleDateFormat sdf = new SimpleDateFormat("d MMM yyyy", LanguageUtils.getCurrentLocale(this));
SimpleDateFormat sdf =
new SimpleDateFormat("d MMM yyyy", LanguageUtils.getCurrentLocale(this));
HistoryListItem.HistoryItem history = new HistoryListItem.HistoryItem(
0L,
ZimContentProvider.getId(),

View File

@ -0,0 +1,73 @@
package org.kiwix.kiwixmobile.main;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
public class OnSwipeTouchListener implements View.OnTouchListener {
private final GestureDetector gestureDetector;
@SuppressLint("SyntheticAccessor")
public OnSwipeTouchListener(Context ctx) {
gestureDetector = new GestureDetector(ctx, new GestureListener());
}
@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
private final class GestureListener extends GestureDetector.SimpleOnGestureListener {
private static final int SWIPE_THRESHOLD = 100;
private static final int SWIPE_VELOCITY_THRESHOLD = 100;
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
boolean result = false;
try {
float diffY = e2.getY() - e1.getY();
float diffX = e2.getX() - e1.getX();
if (Math.abs(diffX) > Math.abs(diffY)) {
if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
if (diffX > 0) {
onSwipeRight();
} else {
onSwipeLeft();
}
result = true;
}
} else if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
if (diffY > 0) {
onSwipeBottom();
} else {
onSwipeTop();
}
result = true;
}
} catch (Exception exception) {
exception.printStackTrace();
}
return result;
}
}
public void onSwipeRight() {
}
public void onSwipeLeft() {
}
public void onSwipeTop() {
}
public void onSwipeBottom() {
}
}

View File

@ -19,9 +19,9 @@
package org.kiwix.kiwixmobile.settings;
import android.app.FragmentManager;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.preference.EditTextPreference;
@ -35,6 +35,7 @@ import android.webkit.WebView;
import android.widget.BaseAdapter;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import eu.mhutti1.utils.storage.StorageDevice;
import eu.mhutti1.utils.storage.StorageSelectDialog;
@ -200,7 +201,7 @@ public class KiwixSettingsActivity extends BaseActivity {
String selectedLang = sharedPreferenceUtil.getPrefLanguage(Locale.getDefault().toString());
List<String> languageCodeList = new LanguageUtils(getActivity()).getKeys();
selectedLang = languageCodeList.contains(selectedLang) ? selectedLang : "en";
String code[] = languageCodeList.toArray(new String[languageCodeList.size()]);
String code[] = languageCodeList.toArray(new String[0]);
String[] entries = new String[code.length];
for (int index = 0; index < code.length; index++) {
Locale locale = new Locale(code[index]);
@ -229,11 +230,16 @@ public class KiwixSettingsActivity extends BaseActivity {
}
private void setAppVersionNumber() {
String version;
version = BuildConfig.VERSION_NAME + " Build: " + BuildConfig.VERSION_CODE;
EditTextPreference versionPref = (EditTextPreference) PrefsFragment.this
.findPreference(PREF_VERSION);
versionPref.setSummary(version);
EditTextPreference versionPref = (EditTextPreference) findPreference(PREF_VERSION);
versionPref.setSummary(BuildConfig.VERSION_NAME + " Build: " + getVersionCode());
}
private int getVersionCode() {
try {
return getActivity().getPackageManager().getPackageInfo(getActivity().getPackageName(), 0).versionCode;
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
@ -322,7 +328,6 @@ public class KiwixSettingsActivity extends BaseActivity {
}
public void openFolderSelect() {
FragmentManager fm = getFragmentManager();
StorageSelectDialog dialogFragment = new StorageSelectDialog();
Bundle b = new Bundle();
b.putString(StorageSelectDialog.STORAGE_DIALOG_INTERNAL,
@ -332,7 +337,7 @@ public class KiwixSettingsActivity extends BaseActivity {
b.putInt(StorageSelectDialog.STORAGE_DIALOG_THEME, StyleUtils.dialogStyle());
dialogFragment.setArguments(b);
dialogFragment.setOnSelectListener(this);
dialogFragment.show(fm, getResources().getString(R.string.pref_storage));
dialogFragment.show(((AppCompatActivity) getActivity()).getSupportFragmentManager(), getResources().getString(R.string.pref_storage));
}
@Override

View File

@ -66,8 +66,6 @@ public final class Constants {
public static final String PREF_WIFI_ONLY = "pref_wifi_only";
public static final String PREF_BOTTOM_TOOLBAR = "pref_bottomtoolbar";
public static final String PREF_KIWIX_MOBILE = "kiwix-mobile";
public static final String PREF_BACK_TO_TOP = "pref_backtotop";

View File

@ -4,14 +4,15 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
import android.preference.PreferenceManager;
import io.reactivex.Flowable;
import io.reactivex.processors.BehaviorProcessor;
import io.reactivex.processors.PublishProcessor;
import java.util.Calendar;
import javax.inject.Inject;
import javax.inject.Singleton;
import static org.kiwix.kiwixmobile.utils.Constants.PREF_AUTONIGHTMODE;
import static org.kiwix.kiwixmobile.utils.Constants.PREF_BACK_TO_TOP;
import static org.kiwix.kiwixmobile.utils.Constants.PREF_BOTTOM_TOOLBAR;
import static org.kiwix.kiwixmobile.utils.Constants.PREF_EXTERNAL_LINK_POPUP;
import static org.kiwix.kiwixmobile.utils.Constants.PREF_FULLSCREEN;
import static org.kiwix.kiwixmobile.utils.Constants.PREF_HIDE_TOOLBAR;
@ -35,12 +36,11 @@ public class SharedPreferenceUtil {
private static final String PREF_SHOW_BOOKMARKS_CURRENT_BOOK = "show_bookmarks_current_book";
private static final String PREF_SHOW_HISTORY_CURRENT_BOOK = "show_history_current_book";
private SharedPreferences sharedPreferences;
public final BehaviorProcessor<String> prefStorages;
private final PublishProcessor<String> prefStorages = PublishProcessor.create();
@Inject
public SharedPreferenceUtil(Context context) {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
prefStorages = BehaviorProcessor.createDefault(getPrefStorage());
}
public boolean getPrefWifiOnly() {
@ -59,9 +59,6 @@ public class SharedPreferenceUtil {
return sharedPreferences.getBoolean(PREF_FULLSCREEN, false);
}
public boolean getPrefBottomToolbar() {
return sharedPreferences.getBoolean(PREF_BOTTOM_TOOLBAR, false);
}
public boolean getPrefBackToTop() {
return sharedPreferences.getBoolean(PREF_BACK_TO_TOP, false);
@ -130,6 +127,10 @@ public class SharedPreferenceUtil {
prefStorages.onNext(storage);
}
public Flowable<String> getPrefStorages(){
return prefStorages.startWith(getPrefStorage());
}
public void putPrefFullScreen(boolean fullScreen) {
sharedPreferences.edit().putBoolean(PREF_FULLSCREEN, fullScreen).apply();
}

View File

@ -26,6 +26,7 @@ import android.provider.MediaStore.MediaColumns
import eu.mhutti1.utils.storage.StorageDeviceUtils
import io.reactivex.Flowable
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import org.kiwix.kiwixmobile.extensions.forEachRow
import org.kiwix.kiwixmobile.extensions.get
import java.io.File
@ -37,8 +38,8 @@ class FileSearch @Inject constructor(private val context: Context) {
fun scan(defaultPath: String) =
Flowable.combineLatest(
Flowable.fromCallable { scanFileSystem(defaultPath) },
Flowable.fromCallable(this::scanMediaStore),
Flowable.fromCallable { scanFileSystem(defaultPath) }.subscribeOn(Schedulers.io()),
Flowable.fromCallable(this::scanMediaStore).subscribeOn(Schedulers.io()),
BiFunction<List<File>, List<File>, List<File>> { filesSystemFiles, mediaStoreFiles ->
filesSystemFiles + mediaStoreFiles
}
@ -74,13 +75,19 @@ class FileSearch @Inject constructor(private val context: Context) {
*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()
private fun scanDirectory(directory: String): List<File> = File(directory).listFiles()
?.fold(
mutableListOf(), { acc, file ->
acc.apply {
if (file.isDirectory) {
addAll(scanDirectory(file.path))
} else if (file.extension.isAny(*zimFileExtensions)) {
add(file)
}
}
}) ?: emptyList()
}
internal fun String.endsWithAny(vararg suffixes: String) =
suffixes.fold(false, { acc, s -> acc or endsWith(s) })
internal fun String.isAny(vararg suffixes: String) =
suffixes.firstOrNull { endsWith(it) } != null

View File

@ -1,34 +0,0 @@
package org.kiwix.kiwixmobile.views
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.extensions.inflate
import org.kiwix.kiwixmobile.zim_manager.Language
class LanguageAdapter(val listItems: MutableList<Language>) : RecyclerView.Adapter<LanguageViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
) = LanguageViewHolder(
parent.inflate(R.layout.item_language, false),
this::toggleItemAt
)
override fun getItemCount() = listItems.size
override fun onBindViewHolder(
holder: LanguageViewHolder,
position: Int
) {
holder.bind(listItems[position], position)
}
private fun toggleItemAt(position: Int) {
listItems[position] = listItems[position].also { it.active = !it.active }
notifyItemChanged(position)
}
}

View File

@ -1,66 +0,0 @@
/*
* Kiwix Android
* Copyright (C) 2018 Kiwix <android.kiwix.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.kiwix.kiwixmobile.views
import android.content.Context
import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.language_selection.language_check_view
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.zim_manager.Language
/**
* Created by judebrauer on 12/6/17
*/
class LanguageSelectDialog constructor(
context: Context
) : AlertDialog(context) {
class Builder : AlertDialog.Builder, LayoutContainer {
lateinit var dialogView: View
override val containerView: View? by lazy { dialogView }
lateinit var onOkClicked: (List<Language>) -> Unit
var languages: List<Language> = listOf()
constructor(context: Context) : super(context)
constructor(
context: Context,
themeResId: Int
) : super(context, themeResId)
override fun create(): AlertDialog {
dialogView = View.inflate(context, R.layout.language_selection, null)
val languageArrayAdapter = LanguageAdapter(languages.toMutableList())
language_check_view.run {
adapter = languageArrayAdapter
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
setHasFixedSize(true)
}
setView(dialogView)
setPositiveButton(android.R.string.ok) { _, _ ->
onOkClicked.invoke(languageArrayAdapter.listItems)
}
return super.create()
}
}
}

View File

@ -1,45 +0,0 @@
package org.kiwix.kiwixmobile.views
import android.graphics.Typeface
import android.view.View
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_language.item_language_books_count
import kotlinx.android.synthetic.main.item_language.item_language_checkbox
import kotlinx.android.synthetic.main.item_language.item_language_localized_name
import kotlinx.android.synthetic.main.item_language.item_language_name
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.utils.LanguageUtils
import org.kiwix.kiwixmobile.zim_manager.Language
class LanguageViewHolder(
override val containerView: View,
private val onCheckboxChecked: (Int) -> Unit
) : ViewHolder(containerView),
LayoutContainer {
fun bind(
language: Language,
position: Int
) {
val context = containerView.context
item_language_name.text = language.language
item_language_localized_name.text = context.getString(
R.string.language_localized,
language.languageLocalized
)
item_language_localized_name.typeface = Typeface.createFromAsset(
context.assets,
LanguageUtils.getTypeface(language.languageCode)
)
item_language_books_count.text =
context.getString(R.string.language_count, language.occurencesOfLanguage)
item_language_checkbox.setOnCheckedChangeListener(null)
item_language_checkbox.isChecked = language.active
item_language_checkbox.setOnCheckedChangeListener { _, _ ->
onCheckboxChecked.invoke(position)
}
containerView.setOnClickListener {
item_language_checkbox.toggle()
}
}
}

View File

@ -6,6 +6,7 @@ import java.util.Locale
@Parcelize
data class Language constructor(
val id: Long = 0,
var active: Boolean,
var occurencesOfLanguage: Int,
var language: String,
@ -13,11 +14,14 @@ data class Language constructor(
var languageCode: String,
var languageCodeISO2: String
) : Parcelable {
constructor(
locale: Locale,
active: Boolean,
occurrencesOfLanguage: Int
occurrencesOfLanguage: Int,
id: Long = 0
) : this(
id,
active,
occurrencesOfLanguage,
locale.displayLanguage,
@ -35,4 +39,7 @@ data class Language constructor(
override fun equals(other: Any?): Boolean {
return (other as Language).language == language && other.active == active
}
fun matches(filter: String) =
language.contains(filter, true) or languageLocalized.contains(filter, true)
}

View File

@ -26,33 +26,26 @@ import android.util.Log
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.widget.SearchView
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import io.reactivex.Flowable
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.zim_manager.manageViewPager
import kotlinx.android.synthetic.main.zim_manager.tabs
import kotlinx.android.synthetic.main.zim_manager.toolbar
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.base.BaseActivity
import org.kiwix.kiwixmobile.database.newdb.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.extensions.toast
import org.kiwix.kiwixmobile.extensions.start
import org.kiwix.kiwixmobile.extensions.viewModel
import org.kiwix.kiwixmobile.language.LanguageActivity
import org.kiwix.kiwixmobile.main.MainActivity
import org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX
import org.kiwix.kiwixmobile.utils.LanguageUtils
import org.kiwix.kiwixmobile.utils.StyleUtils.dialogStyle
import org.kiwix.kiwixmobile.views.LanguageSelectDialog
import java.io.File
import javax.inject.Inject
import org.kiwix.kiwixmobile.zim_manager.local_file_transfer.LocalFileTransferActivity;
class ZimManageActivity : BaseActivity() {
private val zimManageViewModel: ZimManageViewModel by lazy {
ViewModelProviders.of(this, viewModelFactory)
.get(ZimManageViewModel::class.java)
}
private val zimManageViewModel by lazy { viewModel<ZimManageViewModel>(viewModelFactory) }
private val mSectionsPagerAdapter: SectionsPagerAdapter by lazy {
SectionsPagerAdapter(this, supportFragmentManager)
}
@ -79,9 +72,6 @@ class ZimManageActivity : BaseActivity() {
tabs.setupWithViewPager(this)
addOnPageChangeListener(SimplePageChangeListener(this@ZimManageActivity::updateMenu))
}
zimManageViewModel.languageItems.observe(this, Observer {
onLanguageItemsForDialogUpdated(it!!)
})
setViewPagerPositionFromIntent(intent)
}
@ -96,24 +86,6 @@ class ZimManageActivity : BaseActivity() {
}
}
private fun onLanguageItemsForDialogUpdated(languages: List<Language>) {
if (languages.isEmpty()) {
toast(R.string.wait_for_load)
} else {
LanguageSelectDialog.Builder(this, dialogStyle())
.apply {
onOkClicked = {
Flowable.fromCallable {
languagesDao.insert(it)
}
.subscribeOn(Schedulers.io())
.subscribe()
}
this.languages = languages
}
.show()
}
}
private fun updateMenu(position: Int) {
searchItem?.isVisible = position == 1
@ -156,9 +128,8 @@ class ZimManageActivity : BaseActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.select_language -> {
zimManageViewModel.requestLanguagesDialog.onNext(Unit)
}
R.id.select_language -> start<LanguageActivity>()
R.id.get_zim_nearby_device -> {
startActivity(Intent(this, LocalFileTransferActivity::class.java));
}

View File

@ -110,13 +110,11 @@ class ZimManageViewModel @Inject constructor(
val deviceListIsRefreshing = MutableLiveData<Boolean>()
val libraryListIsRefreshing = MutableLiveData<Boolean>()
val networkStates = MutableLiveData<NetworkState>()
val languageItems = MutableLiveData<List<Language>>()
val requestFileSystemCheck = PublishProcessor.create<Unit>()
val fileSelectActions = PublishProcessor.create<FileSelectActions>()
val requestDownloadLibrary = BehaviorProcessor.createDefault<Unit>(Unit)
val requestFiltering = BehaviorProcessor.createDefault<String>("")
val requestLanguagesDialog = PublishProcessor.create<Unit>()
private val compositeDisposable = CompositeDisposable()
@ -151,7 +149,6 @@ class ZimManageViewModel @Inject constructor(
updateLibraryItems(booksFromDao, downloads, networkLibrary, languages),
updateLanguagesInDao(networkLibrary, languages),
updateNetworkStates(),
updateLanguageItemsForDialog(languages),
requestsAndConnectivtyChangesToLibraryRequests(networkLibrary),
fileSelectActions()
)
@ -286,16 +283,6 @@ class ZimManageViewModel @Inject constructor(
)
}
private fun updateLanguageItemsForDialog(languages: Flowable<List<Language>>) =
requestLanguagesDialog
.withLatestFrom(
languages,
BiFunction<Unit, List<Language>, List<Language>> { _, langs -> langs })
.subscribe(
languageItems::postValue,
Throwable::printStackTrace
)
private fun updateNetworkStates() =
connectivityBroadcastReceiver.networkStates.subscribe(
networkStates::postValue, Throwable::printStackTrace

View File

@ -14,11 +14,11 @@ import javax.inject.Inject
class StorageObserver @Inject constructor(
private val sharedPreferenceUtil: SharedPreferenceUtil,
downloadDao: NewDownloadDao,
private val downloadDao: NewDownloadDao,
private val fileSearch: FileSearch
) {
val booksOnFileSystem = scanFiles()
val booksOnFileSystem get() = scanFiles()
.withLatestFrom(
downloadDao.downloads(),
BiFunction(this::toFilesThatAreNotDownloading)

View File

@ -29,7 +29,6 @@ import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.disposables.CompositeDisposable
@ -40,6 +39,7 @@ import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.base.BaseFragment
import org.kiwix.kiwixmobile.di.components.ActivityComponent
import org.kiwix.kiwixmobile.extensions.toast
import org.kiwix.kiwixmobile.extensions.viewModel
import org.kiwix.kiwixmobile.utils.Constants.REQUEST_STORAGE_PERMISSION
import org.kiwix.kiwixmobile.utils.LanguageUtils
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
@ -61,11 +61,9 @@ class ZimFileSelectFragment : BaseFragment() {
private var actionMode: ActionMode? = null
val disposable = CompositeDisposable()
private val zimManageViewModel: ZimManageViewModel by lazy {
ViewModelProviders.of(activity!!, viewModelFactory)
.get(ZimManageViewModel::class.java)
private val zimManageViewModel by lazy {
activity!!.viewModel<ZimManageViewModel>(viewModelFactory)
}
private val bookDelegate: BookDelegate by lazy {
BookDelegate(sharedPreferenceUtil,
{ offerAction(RequestOpen(it)) },

View File

@ -47,7 +47,7 @@ sealed class BookOnDiskDelegate<I : BooksOnDiskListItem, VH : BookOnDiskViewHold
viewHolder: ViewHolder,
itemToBind: BooksOnDiskListItem
) {
(viewHolder as BookOnDiskViewHolder.BookViewHolder).bind((itemToBind as BookOnDisk), selectionMode)
(viewHolder as BookViewHolder).bind((itemToBind as BookOnDisk), selectionMode)
}
override fun createViewHolder(parent: ViewGroup) =

View File

@ -5,6 +5,7 @@ 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_clickable_area
import kotlinx.android.synthetic.main.item_book.item_book_date
import kotlinx.android.synthetic.main.item_book.item_book_description
import kotlinx.android.synthetic.main.item_book.item_book_icon
@ -74,13 +75,13 @@ sealed class BookOnDiskViewHolder<T : BooksOnDiskListItem>(containerView: View)
when (selectionMode) {
MULTI -> {
itemBookCheckbox.visibility = View.VISIBLE
containerView.setOnClickListener { multiSelectAction?.invoke(item) }
containerView.setOnLongClickListener(null)
item_book_clickable_area.setOnClickListener { multiSelectAction?.invoke(item) }
item_book_clickable_area.setOnLongClickListener(null)
}
NORMAL -> {
itemBookCheckbox.visibility = View.GONE
containerView.setOnClickListener { clickAction.invoke(item) }
containerView.setOnLongClickListener {
item_book_clickable_area.setOnClickListener { clickAction.invoke(item) }
item_book_clickable_area.setOnLongClickListener {
longClickAction?.invoke(item)
return@setOnLongClickListener true
}

View File

@ -17,6 +17,7 @@
*/
package org.kiwix.kiwixmobile.zim_manager.library_view
import android.annotation.SuppressLint
import android.net.ConnectivityManager
import android.os.Bundle
import android.view.LayoutInflater
@ -26,11 +27,10 @@ import android.view.View.VISIBLE
import android.view.ViewGroup
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import eu.mhutti1.utils.storage.StorageDevice
import eu.mhutti1.utils.storage.support.StorageSelectDialog
import eu.mhutti1.utils.storage.StorageSelectDialog
import kotlinx.android.synthetic.main.activity_library.libraryErrorText
import kotlinx.android.synthetic.main.activity_library.libraryList
import kotlinx.android.synthetic.main.activity_library.librarySwipeRefresh
@ -41,6 +41,7 @@ import org.kiwix.kiwixmobile.di.components.ActivityComponent
import org.kiwix.kiwixmobile.downloader.Downloader
import org.kiwix.kiwixmobile.extensions.snack
import org.kiwix.kiwixmobile.extensions.toast
import org.kiwix.kiwixmobile.extensions.viewModel
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.main.MainActivity
import org.kiwix.kiwixmobile.utils.BookUtils
@ -71,10 +72,10 @@ class LibraryFragment : BaseFragment() {
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
@Inject lateinit var bookUtils: BookUtils
private val zimManageViewModel: ZimManageViewModel by lazy {
ViewModelProviders.of(activity!!, viewModelFactory)
.get(ZimManageViewModel::class.java)
private val zimManageViewModel by lazy {
activity!!.viewModel<ZimManageViewModel>(viewModelFactory)
}
private val libraryAdapter: LibraryAdapter by lazy {
LibraryAdapter(
BookDelegate(bookUtils, this::onBookItemClick), DividerDelegate
@ -209,8 +210,10 @@ class LibraryFragment : BaseFragment() {
private fun notEnoughSpaceAvailable(item: BookItem) =
spaceAvailable < item.book.size.toLong() * 1024f
@SuppressLint("ImplicitSamInstance")
private fun showStorageSelectDialog() {
StorageSelectDialog().apply {
StorageSelectDialog()
.apply {
arguments = Bundle().apply {
putString(
StorageSelectDialog.STORAGE_DIALOG_INTERNAL,

View File

@ -25,7 +25,10 @@ interface AbsDelegateAdapter<INSTANCE : SUPERTYPE,
SUPERTYPE : Any,
VIEWHOLDER : BaseViewHolder<INSTANCE>> :
AdapterDelegate<SUPERTYPE> {
abstract val itemClass: Class<INSTANCE>
val itemClass: Class<INSTANCE>
@Suppress("UNCHECKED_CAST")
override fun bind(
viewHolder: RecyclerView.ViewHolder,
itemToBind: SUPERTYPE

View File

@ -27,7 +27,7 @@ class AdapterDelegateManager<T>() {
private fun getDelegateIndexFor(item: T): Int {
for (index in 0..delegates.size()) {
val valueAt = delegates.valueAt(index)
if (valueAt.isFor(item)) {
if (valueAt?.isFor(item) == true) {
return index;
}
}

View File

@ -3,7 +3,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/listBackground"
android:fitsSystemWindows="true"
>
@ -26,7 +25,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
@ -34,4 +33,5 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay"
app:layout_constraintTop_toTopOf="parent"
>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/AppTheme.AppBarOverlay"
app:popupTheme="@style/AppTheme.PopupOverlay"
/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay"
app:layout_constraintTop_toTopOf="parent"
>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/AppTheme.AppBarOverlay"
app:popupTheme="@style/AppTheme.PopupOverlay"
/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
/>
<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/language_progressbar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<TextView
android:id="@+id/file_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:textSize="18sp"
/>
<TextView
android:id="@+id/file_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_toEndOf="@id/file_name"
android:gravity="end"
android:padding="10dp"
android:textSize="18sp"
android:layout_toRightOf="@id/file_name"
/>
</RelativeLayout>

View File

@ -4,7 +4,6 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:paddingEnd="0dp"
android:paddingLeft="0dp"
android:paddingRight="0dp"
@ -135,5 +134,14 @@
app:layout_constraintStart_toStartOf="@id/item_book_title"
app:layout_constraintTop_toBottomOf="@id/item_book_label_picture"
/>
<View
android:background="?android:attr/selectableItemBackground"
android:id="@+id/item_book_clickable_area"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -57,4 +57,9 @@
app:layout_constraintTop_toTopOf="parent"
tools:text="9 books"
/>
<View
android:background="?android:attr/selectableItemBackground"
android:id="@+id/item_language_clickable_area"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/language_check_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -15,7 +15,6 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp"
>
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:textAlignment="center"
android:textSize="22sp"
/>
<ListView
android:id="@+id/device_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
</ListView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ems="10"
android:inputType="textPersonName"
android:text="@string/slash"
android:importantForAutofill="no"
tools:targetApi="o"
/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/plus"
/>
</LinearLayout>
</LinearLayout>

View File

@ -94,7 +94,6 @@
<string name="zim_novid">لا يوجد فيديو</string>
<string name="no_network_connection">لا يوجد اتصال بالشبكة</string>
<string name="get_library_over_network">استخدم الشبكة لتنزيل قائمة المحتويات. (حوالي 6 ميغابايت)</string>
<string name="wait_for_load">لا يزال يتم تحميل المحتوى</string>
<string name="help_2">ماذا يفعل كيويكس؟</string>
<string name="help_3">كيويكس قارئ محتوى غير متصل، وهو يعمل إلى حد كبير مثل المتصفح، ولكن بدلا من الوصول إلى صفحات الويب على الإنترنت، فإنه يقرأ المحتوى من ملف بتنسيق ZIM.</string>
<string name="help_4">بينما كان كيويكس مصمما في الأصل لتوفير ويكيبيديا في وضع عدم الاتصال، فإنه يقرأ أيضا محتويات أخرى.</string>
@ -138,8 +137,6 @@
<string name="time_minute">دق</string>
<string name="time_second">ث</string>
<string name="time_left">متبقي</string>
<string name="pref_bottomtoolbar">تمكين شريط الأدوات السفلي</string>
<string name="pref_bottomtoolbar_summary">عرض شريط أدوات بإجراءات سريعة في الجزء السفلي من الشاشة</string>
<string name="pref_autonightmode_summary">التبديل تلقائيا بين وضع اليلي والنهاري.</string>
<string name="pref_autonightmode">الوضع الليلي الآلي</string>
<string name="pref_external_link_popup_title">التحذير عند إدخال وصلات خارجية</string>

View File

@ -94,7 +94,6 @@
<string name="zim_novid">Без видео клипове</string>
<string name="no_network_connection">Няма връзка с мрежата</string>
<string name="get_library_over_network">Използвайте мрежата за даунлоуд на списъка със съдържанието.\n(Приблизително 6MB)</string>
<string name="wait_for_load">Съдържанието все още се зарежда</string>
<string name="help_2">Какво прави Kiwix?</string>
<string name="help_3">Kiwix е офлайн четец на съдържание. Той действа като браузър, но вместо да зарежда съдържанието от онлайн уеб страници, той зарежда съдържанието от файл в ZIM формат.</string>
<string name="help_4">Въпреки че Kiwix е проектиран първоначално, да предоставя достъп до Уикипедия офлайн, той е способен, да предоставя достъп и до друго съдържание.</string>
@ -138,8 +137,6 @@
<string name="time_minute">мин.</string>
<string name="time_second">сек.</string>
<string name="time_left">остават</string>
<string name="pref_bottomtoolbar">Позволете туулбара от долната страна</string>
<string name="pref_bottomtoolbar_summary">Показва туулбар с бързи действия от долната страна на екрана</string>
<string name="pref_autonightmode_summary">Превключвай автоматично между дневен и нощен режим.</string>
<string name="pref_autonightmode">Автоматичен нощен режим.</string>
<string name="pref_external_link_popup_title">При зареждане на външни препратки</string>

View File

@ -94,7 +94,6 @@
<string name="zim_novid">কোন ভিডিও নেই</string>
<string name="no_network_connection">কোন নেটওয়ার্ক সংযোগ নেই</string>
<string name="get_library_over_network">বিষয়বস্তুর তালিকা ডাউনলোড করতে নেটওয়ার্ক ব্যবহার করুন (আনুমানিক ৬ MB)</string>
<string name="wait_for_load">বিষয়বস্তু এখনো লোড হচ্ছে</string>
<string name="help_2">কিউইক্স কি কাজ করে?</string>
<string name="help_3">কিউইক্স একটি অফলাইনভিত্তিক নিবন্ধ পাঠক। এটি একটি ব্রাউজারের মত অনেকটা কাজ করে কিন্তু অনলাইনে ওয়েব পৃষ্ঠাগুলিতে ঢুকার পরিবর্তে, এটি ZIM ফরম্যাটে একটি ফাইল থেকে বিষয়বস্তু পড়ে।</string>
<string name="help_4">কিউইক্স মূলত উইকিপিডিয়াকে অফলাইনে সরবরাহ করার জন্য নকশা করা হয়েছে, তবে এটি অন্যান্য বিষয়গুলিও পড়ে থাকে।</string>
@ -140,7 +139,6 @@
<string name="time_left">বামে</string>
<string name="pref_autonightmode_summary">স্বয়ংক্রিয়ভাবে দিন এবং রাতের মোডে স্যুইচ করুন।</string>
<string name="pref_autonightmode">স্বয়ংক্রিয় রাত্রি মোড</string>
<string name="language_count">(%1$dটি)</string>
<string name="do_not_ask_anymore">আর জিজ্ঞাসা করবেন না</string>
<string name="your_languages">নির্বাচিত ভাষা</string>
<string name="other_languages">অন্যান্য ভাষাসমূহ:</string>

View File

@ -94,7 +94,6 @@
<string name="zim_novid">Video ebet</string>
<string name="no_network_connection">N\'eus kevreadur rouedad ebet</string>
<string name="get_library_over_network">Implijout ar rouedad evit pellgargañ ar roll endalc\'had (war-dro 6Mo).</string>
<string name="wait_for_load">O kargañ emañ an endalc\'had c\'hoazh</string>
<string name="help_2">Petra a ra Kiwix ?</string>
<string name="help_3">Ul lenner endalc\'had ezlinenn eo Kiwix. Ober a ra traoù hogos evel ur merdeer, met e-lec\'h diraez pajennoù web enlinenn e lenn an endalc\'had diwar ur restr er furmad ZIM.</string>
<string name="help_4">Daoust ma\'z eo bet krouet Kiwix evit pourchas Wikipedia ezlinenn e lenn endalc\'hioù all ivez.</string>
@ -135,8 +134,6 @@
<string name="pref_wifi_only">Pellgargañ an endalc\'had dre WiFi hepken</string>
<string name="time_day">deiz</string>
<string name="time_left">kleiz</string>
<string name="pref_bottomtoolbar">Gweredekaat ar varrenn ostilhoù en traoñ</string>
<string name="pref_bottomtoolbar_summary">Diskwel ur varrenn ostilhoù gant oberoù prim e traoñ ar skramm</string>
<string name="pref_autonightmode_summary">Cheñch ent emgefre etre ar modoù noz ha deiz.</string>
<string name="pref_autonightmode">Mod noz emgefreek</string>
<string name="pref_external_link_popup_title">Kemenn pa\'z eer liammoù diavaez</string>

View File

@ -89,7 +89,6 @@
<string name="zim_novid">Ingen videoer</string>
<string name="no_network_connection">Ingen netværksforbindelse</string>
<string name="get_library_over_network">Brug netværk til at hente indholdsliste. (Cirka 6 MB)</string>
<string name="wait_for_load">Indhold indlæses stadig</string>
<string name="help_2">Hvad gør Kiwix?</string>
<string name="help_3">Kiwix er en lokal indholdlæser. Den fungerer meget lig en browser, men i stedet for at tilgå internetsider, så læser den indhold fra en fil i ZIM-formatet.</string>
<string name="help_4">Selvom Kiwix oprindelig blev designet for at tilbyde Wikipedia lokalt, så læser det også andet indhold.</string>
@ -129,8 +128,6 @@
<string name="time_day">dag</string>
<string name="time_hour">t</string>
<string name="time_left">tilbage</string>
<string name="pref_bottomtoolbar">Aktiver knapværktøjslinje</string>
<string name="pref_bottomtoolbar_summary">Vis en værktøjslinje med hurtige handling i bunden af skærmen</string>
<string name="pref_autonightmode_summary">Skift automatisk mellem dag- og nattilstand.</string>
<string name="pref_autonightmode">Automatisk nattilstand</string>
<string name="pref_external_link_popup_title">Advar når man bruger eksterne henvisninger</string>

View File

@ -90,7 +90,6 @@
<string name="zim_novid">Keine Videos</string>
<string name="no_network_connection">Keine Netzwerkverbindung</string>
<string name="get_library_over_network">Netzwerk verwenden, um die Inhaltsliste herunterzuladen (ungefähr 6 MB).</string>
<string name="wait_for_load">Inhalt lädt noch</string>
<string name="help_2">Was macht Kiwix?</string>
<string name="help_3">Kiwix ist eine Lesesoftware für Offlineinhalte. Sie funktioniert wie ein Browser, aber anstatt auf Webseiten online zuzugreifen, liest sie Inhalte von einer Datei im ZIM-Format.</string>
<string name="help_4">Obwohl Kiwix ursprünglich entwickelt wurde, um Wikipedia offline zur Verfügung zu stellen, liest es auch andere Inhalte.</string>
@ -134,8 +133,6 @@
<string name="time_minute">Minuten</string>
<string name="time_second">Sekunden</string>
<string name="time_left">verbleibend</string>
<string name="pref_bottomtoolbar">Untere Werkzeugleiste aktivieren</string>
<string name="pref_bottomtoolbar_summary">Eine Werkzeugleiste mit Schnellzugriffsaktionen am unteren Bildschirmrand anzeigen</string>
<string name="pref_autonightmode_summary">Automatisch zwischen Tag- und Nachtmodus umschalten.</string>
<string name="pref_autonightmode">Automatischer Nachtmodus</string>
<string name="pref_external_link_popup_title">Beim Einfügen von externen Links warnen</string>

View File

@ -85,7 +85,6 @@
<string name="zim_novid">Χωρίς βίντεο</string>
<string name="no_network_connection">Δεν υπάρχει σύνδεση στο δίκτυο</string>
<string name="get_library_over_network">Χρησιμοποιήστε δίκτυο για να κατεβάσετε την λίστα περιεχομένων. (Περίπου 6MB)</string>
<string name="wait_for_load">Το περιεχόμενο φορτώνει ακόμα</string>
<string name="help_2">Τι κάνει το Kiwix;</string>
<string name="help_3">Το Kiwix είναι ένας αναγνώστης περιεχομένου εκτός σύνδεσης. Φέρεται πολύ σαν περιηγητής αλλά αντί να έχει πρόσβαση σε διαδικτυακές σελίδες, διαβάζει περιεχόμενο από ένα αρχείο σε μορφοποίηση ZIM.</string>
<string name="help_5">Που είναι το περιεχόμενο;</string>

View File

@ -94,7 +94,6 @@
<string name="zim_novid">Neniu Videaĵo</string>
<string name="no_network_connection">Reta konekto mankas</string>
<string name="get_library_over_network">Uzi reton por elŝuti liston de enhavo (proksimume 6 megabajtoj)</string>
<string name="wait_for_load">Enhavo Estas Ankoraŭ Ŝarĝata</string>
<string name="help_2">Kion faras Kiwix?</string>
<string name="help_3">Kiwix estas senkonekta legilo de enhavo. Ĝi estas simila al TTT-legilo, sed anstataŭ legi retpaĝojn konektite, ĝi legas enhavon en ZIM-dosiero.</string>
<string name="help_4">Kiwix estis originale farita por senkonekta legado de Vikipedio, sed ĝi povas legi alian enhavon.</string>
@ -135,8 +134,6 @@
<string name="pref_wifi_only">Elŝuti nur per vifio</string>
<string name="time_day">tago</string>
<string name="time_left">restas</string>
<string name="pref_bottomtoolbar">Ŝalti malsupran ilobreton</string>
<string name="pref_bottomtoolbar_summary">Montri ilobreton kun rapidaj agoj ĉe la malsupro de la ekrano</string>
<string name="pref_autonightmode_summary">Aŭtomate ŝalti tagan/noktan reĝimon</string>
<string name="pref_autonightmode">Aŭtomata nokta reĝimo</string>
<string name="pref_external_link_popup_title">Averti pri aliro al eksteraj ligiloj</string>

View File

@ -92,7 +92,6 @@
<string name="zim_novid">Ningún video</string>
<string name="no_network_connection">Ninguna conexión de red</string>
<string name="get_library_over_network">Usar la red para descargar la lista de contenido. (Aproximadamente 6MB)</string>
<string name="wait_for_load">Todavía está cargando el contenido</string>
<string name="help_2">¿Qué hace Kiwix?</string>
<string name="help_3">Kiwix es un lector de contenido desconectado. Actúa mucho como un navegador pero en vez de acceder páginas web en línea, lee contenido de un archivo en formato ZIM.</string>
<string name="help_4">Si bien Kiwix se diseñó originalmente para proporcionar una Wikipedia sin conexión, también puede leer otros contenidos.</string>
@ -132,8 +131,6 @@
<string name="pref_wifi_only">Descargar contenido solo mediante wifi</string>
<string name="time_day">día</string>
<string name="time_left">faltan</string>
<string name="pref_bottomtoolbar">Activar barra de herramientas inferior</string>
<string name="pref_bottomtoolbar_summary">Mostrar una barra con acciones rápidas en la parte inferior de la pantalla</string>
<string name="pref_autonightmode_summary">Cambiar automáticamente entre el modo diurno y el nocturno</string>
<string name="pref_autonightmode">Modo nocturno automatizado</string>
<string name="pref_external_link_popup_title">Advertir al seguir enlaces externos</string>

View File

@ -93,7 +93,6 @@
<string name="zim_novid">Bideorik ez</string>
<string name="no_network_connection">Ez dago konexiorik</string>
<string name="get_library_over_network">Sarea erabili eduki zerrenda deskargatzeko. (6MB inguru)</string>
<string name="wait_for_load">Oraindik Edukia Kargatzen</string>
<string name="help_2">Zer egiten du Kiwix-ek?</string>
<string name="help_3">Kiwix konexio gabeko eduki irakurlea da. Nabigatzaile baten moduan erabiltzen da, baina sarean dauden web orrialdeetan sartu beharrean, ZIM formatoko edukia irakurtzen du.</string>
<string name="help_4">Nahiz eta, Kiwix, berez, Wikipedia konexiorik gabe erabiltzeko sortu, beste edukiak ere eskeintzen ditu.</string>
@ -134,8 +133,6 @@
<string name="time_day">eguna</string>
<string name="time_second">k</string>
<string name="time_left">joanda</string>
<string name="pref_bottomtoolbar">Gaitu beheko tresna-barra</string>
<string name="pref_bottomtoolbar_summary">Gaitu ekintza azkarretako tresna-barra pantailaren beheko partean</string>
<string name="pref_autonightmode_summary">Automatikoki piztu egun eta gauaren arteko modoa.</string>
<string name="pref_autonightmode">Gaueko modo automatikoa</string>
<string name="pref_external_link_popup_title">Kanpo esteketara sartzerakoan ohartarazi</string>

View File

@ -87,7 +87,6 @@
<string name="zim_novid">Ei videoita</string>
<string name="no_network_connection">Ei verkkoyhteyttä</string>
<string name="get_library_over_network">Lataa sisältöluettelo käyttämällä verkkoa. (Noin 6 Mt)</string>
<string name="wait_for_load">Sisältö latautuu edelleen</string>
<string name="help_2">Mitä Kiwix tekee?</string>
<string name="help_5">Missä sisältö on?</string>
<string name="help_6">Sisältöämme ylläpidetään Kiwixin verkkosivulla.</string>

View File

@ -87,7 +87,6 @@
<string name="zim_novid">Aucune vidéo</string>
<string name="no_network_connection">Aucune connexion réseau</string>
<string name="get_library_over_network">Utiliser le réseau pour télécharger la liste de contenu (environ 6Mo).</string>
<string name="wait_for_load">Le contenu est encore en cours de chargement</string>
<string name="help_2">Que fait Kiwix?</string>
<string name="help_3">Kiwix est un lecteur de contenu hors connexion. Il agit quasiment comme un navigateur, mais au lieu daccéder à des pages web en ligne, il lit le contenu depuis un fichier au format ZIM.</string>
<string name="help_4">Bien que Kiwix ait été conçu au départ pour fournir Wikipédia hors connexion, il lit aussi dautre contenu.</string>
@ -126,8 +125,6 @@
<string name="pref_wifi_only">Télécharger le contenu uniquement via WiFi</string>
<string name="time_day">jour</string>
<string name="time_left">restante(s)</string>
<string name="pref_bottomtoolbar">Activer la barre doutils en bas</string>
<string name="pref_bottomtoolbar_summary">Afficher une barre doutils avec des actions rapides en bas de lécran</string>
<string name="pref_autonightmode_summary">Basculer automatiquement entre les modes jour et nuit.</string>
<string name="pref_autonightmode">Mode nuit automatisé</string>
<string name="pref_external_link_popup_title">Avertir lors de l\'accès à des liens externes</string>

View File

@ -91,7 +91,6 @@
<string name="zim_novid">Sen vídeos</string>
<string name="no_network_connection">Ningunha conexión de rede</string>
<string name="get_library_over_network">Usar a rede para descargar a lista de contido. (Aproximadamente 6MB)</string>
<string name="wait_for_load">Aínda está cargando o contido</string>
<string name="help_2">Que fai Kiwix?</string>
<string name="help_3">Kiwix é un lector de contido desconectado. Actúa como un navegador web, pero en vez de acceder a páxinas web en liña, le o contido dun ficheiro en formato ZIM.</string>
<string name="help_4">Se ben Kiwix deseñouse orixinalmente para proporcionar Wikipedia sen conexión, tamén pode ler outros contidos.</string>
@ -132,8 +131,6 @@
<string name="pref_wifi_only">Descargar contido só mediante WiFi</string>
<string name="time_day">día</string>
<string name="time_left">esquerda</string>
<string name="pref_bottomtoolbar">Activar a barra de ferramentas inferior</string>
<string name="pref_bottomtoolbar_summary">Mostra unha barra de ferramentas con as accións rápidas na parte inferior da pantalla</string>
<string name="pref_autonightmode_summary">Cambiar automaticamente entre o modo diurno e o nocturno</string>
<string name="pref_autonightmode">Modo nocturno automatizado</string>
<string name="pref_external_link_popup_title">Avisar cando introducir ligazóns externas</string>

View File

@ -94,7 +94,6 @@
<string name="zim_novid">चलचित्र नहीं</string>
<string name="no_network_connection">नेटवर्क कनेक्शन नहीं</string>
<string name="get_library_over_network">सामग्री सूची डाउनलोड करने के लिए नेटवर्क का उपयोग करें। (लगभग 6 एमबी</string>
<string name="wait_for_load">सामग्री अभी भी लोड हो रही</string>
<string name="help_2">किविक्स क्या करता है?</string>
<string name="help_3">किवीक्स ऑफ़लाइन सामग्री रीडर है यह एक ब्राउज़र की तरह बहुत ज्यादा काम करता है लेकिन ऑनलाइन वेब पेजों को एक्सेस करने के बजाय, यह ZIM प्रारूप में फ़ाइल से सामग्री पढ़ता है।</string>
<string name="help_4">जबकि किवीक्स को मूल रूप से विकिपीडिया ऑफ़लाइन उपलब्ध कराने के लिए डिज़ाइन किया गया है, यह अन्य सामग्री भी पढ़ता है।</string>
@ -138,8 +137,6 @@
<string name="time_minute">मिन</string>
<string name="time_second">स्</string>
<string name="time_left">बाएँ ओर</string>
<string name="pref_bottomtoolbar">तलस्त साधनपट्टी को सक्षम करें</string>
<string name="pref_bottomtoolbar_summary">स्क्रीन के ताल में त्वरित-कार्य सुविधा युक्त साधनपट्टी को प्रदर्शित करें</string>
<string name="pref_autonightmode_summary">स्वचालित रूप से दिन और रात मोड के बीच स्विच करें।</string>
<string name="pref_autonightmode">स्वचालित रात मोड</string>
<string name="pref_external_link_popup_title">बाह्य कड़ियों में प्रवेश करने पर सावधान करें</string>

View File

@ -92,7 +92,6 @@
<string name="zim_novid">Nincsenek videók</string>
<string name="no_network_connection">Nincs hálózati kapcsolat</string>
<string name="get_library_over_network">Használd a hálózatot a tartalomlista letöltéséhez. (Körülbelül 6MB)</string>
<string name="wait_for_load">A tartalom még töltődik</string>
<string name="help_2">Mit csinál a Kiwix?</string>
<string name="help_3">A Kiwix egy offline tartalom olvasó. Úgy viselkedik, mint egy böngésző, de az online weboldalak betöltése helyett a tartalmát egy ZIM formátumú fájlból olvassa.</string>
<string name="help_4">Bár a Kiwix eredetileg az offline Wikipédia megvalósítására lett tervezve, más tartalmakat is olvas.</string>
@ -136,8 +135,6 @@
<string name="time_minute">perc</string>
<string name="time_second">másodperc</string>
<string name="time_left">maradt</string>
<string name="pref_bottomtoolbar">Alsó eszköztár engedélyezése</string>
<string name="pref_bottomtoolbar_summary">Jeleníts meg a képernyő alján egy eszköztárat a gyors műveleteknek</string>
<string name="pref_autonightmode_summary">Automatikusan vált nappali és éjszakai mód között.</string>
<string name="pref_autonightmode">Automatikus éjszakai mód</string>
<string name="pref_external_link_popup_title">Figyelmeztess külső hivatkozások indítása esetén</string>

View File

@ -94,7 +94,6 @@
<string name="zim_novid">동영상 없음</string>
<string name="no_network_connection">네트워크 연결 없음</string>
<string name="get_library_over_network">콘텐츠 목록의 다운로드를 위해 네트워크를 사용합니다. (약 6MB)</string>
<string name="wait_for_load">내용을 계속 불러오고 있습니다</string>
<string name="help_2">Kiwix의 역할이 무엇입니까?</string>
<string name="help_3">Kiwix는 오프라인 콘텐츠 리더입니다. 브라우저와 매우 비슷하게 동작하지만 온라인 웹 페이지에 접근하는 대신 ZIM 포맷의 파일로부터 내용을 읽습니다.</string>
<string name="help_4">Kiwix가 본래 오프라인 상태에서 위키백과를 제공하도록 설계되었으나 다른 콘텐츠도 읽을 수 있습니다.</string>
@ -138,8 +137,6 @@
<string name="time_minute"></string>
<string name="time_second"></string>
<string name="time_left">남음</string>
<string name="pref_bottomtoolbar">하단 도구 모음 사용</string>
<string name="pref_bottomtoolbar_summary">화면 하단에 빠른 동작들이 포함된 도구 모음을 표시합니다</string>
<string name="pref_autonightmode_summary">야간 모드와 낮 모드를 자동으로 전환합니다.</string>
<string name="pref_autonightmode">자동 야간 모드</string>
<string name="pref_external_link_popup_title">외부 링크에 진입할 때 경고합니다</string>

View File

@ -96,8 +96,6 @@
<string name="time_hour">o</string>
<string name="time_minute">m</string>
<string name="time_left">links</string>
<string name="pref_bottomtoolbar">Zèt de gereidsjapsbalk óngeraan aan</string>
<string name="pref_bottomtoolbar_summary">Tuin \'ne gereidsjapsbalk mit snel hanjelinge óngeraan \'t sjerm</string>
<string name="pref_autonightmode_summary">Sjakel autematis tösse daag- en nachmodus</string>
<string name="pref_autonightmode">Automatiseerde nachmodus</string>
<string name="pref_external_link_popup_title">Waorsjoewing mich wen ich nao \'nen externe link gaon</string>

View File

@ -94,7 +94,6 @@
<string name="zim_novid">Нема видеа</string>
<string name="no_network_connection">Немате мрежна врска</string>
<string name="get_library_over_network">Користи мрежа за преземање на списокот содржини (прибл. 6 МБ)</string>
<string name="wait_for_load">Содржината сè уште се вчитува</string>
<string name="help_2">Што прави Kiwix?</string>
<string name="help_3">Kiwix е вонмрежен читач на содржини. Работи како прелистувач но чита содржини од податотека во ZIM-формат место од семрежјето.</string>
<string name="help_4">Иако Kiwix е првично предвиден како вонмрежен читач на Википедија, но сега чита и други содржини.</string>
@ -138,8 +137,6 @@
<string name="time_minute">мин</string>
<string name="time_second">с</string>
<string name="time_left">лево</string>
<string name="pref_bottomtoolbar">Вклучи алатник најдолу</string>
<string name="pref_bottomtoolbar_summary">Прикажи алатник за брзи дејства најдолу во екранот.</string>
<string name="pref_autonightmode_summary">Автоматски префрлај меѓу дневен и ноќен режим.</string>
<string name="pref_autonightmode">Автоматски ноќен режим</string>
<string name="pref_external_link_popup_title">Предупреди ме кога се внесуваат надворешни врски</string>

View File

@ -109,8 +109,6 @@
<string name="pref_wifi_only">Pobierz zawartość tylko poprzez WiFi</string>
<string name="time_day">dzień</string>
<string name="time_left">do lewej</string>
<string name="pref_bottomtoolbar">Włącz dolny pasek narzędzi</string>
<string name="pref_bottomtoolbar_summary">Wyświetl pasek narzędzi z szybkimi akcjami u dołu ekranu</string>
<string name="pref_autonightmode_summary">Przełączaj automatycznie między trybami dnia i nocy.</string>
<string name="pref_autonightmode">Zautomatyzowany tryb nocny.</string>
<string name="do_not_ask_anymore">Nie pytaj więcej</string>

View File

@ -60,7 +60,6 @@
<string name="zim_simple">اسان</string>
<string name="zim_nopic">هيڅ دوتنه نشته</string>
<string name="zim_novid">هيڅ ويډيو نسته</string>
<string name="wait_for_load">منځپانګې تر وسه هم حرکت کوي</string>
<string name="tts_pause">تفریحي درونه</string>
<string name="tts_resume">انتظارول</string>
<string name="tts_stop">درول</string>

View File

@ -93,7 +93,6 @@
<string name="zim_novid">Sem vídeos</string>
<string name="no_network_connection">Nenhuma ligação de rede</string>
<string name="get_library_over_network">Usar a rede para descarregar a lista de conteúdos (aproximadamente 6 MB).</string>
<string name="wait_for_load">Ainda está a carregar o conteúdo</string>
<string name="help_2">O que faz o Kiwix?</string>
<string name="help_3">O Kiwix é um leitor de conteúdo offline. Funciona tal como um navegador mas, em vez de aceder a páginas da Internet online, lê o conteúdo de um ficheiro no formato ZIM.</string>
<string name="help_4">Embora o Kiwix tenha sido originalmente desenhado para fornecer a Wikipédia offline, também lê outros conteúdos.</string>
@ -134,8 +133,6 @@
<string name="pref_wifi_only">Descarregar conteúdo apenas por WiFi</string>
<string name="time_day">dia</string>
<string name="time_left">esquerda</string>
<string name="pref_bottomtoolbar">Ativar a barra de ferramentas na parte inferior</string>
<string name="pref_bottomtoolbar_summary">Mostrar uma barra de ferramentas com as ações rápidas na parte inferior do ecrã</string>
<string name="pref_autonightmode_summary">Alternar automaticamente entre o modo diurno e noturno.</string>
<string name="pref_autonightmode">Modo noturno automático</string>
<string name="pref_external_link_popup_title">Avisar quando introduzir hiperligações externas</string>

View File

@ -91,7 +91,6 @@
<string name="zim_novid">Fără video</string>
<string name="no_network_connection">Nu există conexiune de rețea</string>
<string name="get_library_over_network">Folosește rețeaua să descarci lista de conținut. (Aproximativ 6MB)</string>
<string name="wait_for_load">Conținutul încă se încarcă</string>
<string name="help_2">Ce face Kiwix?</string>
<string name="help_3">Kiwix este un server de citit conținut off-line. Este aproape ca un browser dar în loc să acceseze paginile web online citește conținut de la un fișier în formatul ZIM.</string>
<string name="help_4">În timp ce Kiwix a fost făcut inițial să ofere Wikipedia off-line de asemenea citește și alt conținut.</string>
@ -130,8 +129,6 @@
<string name="pref_wifi_only">Descarcă conținut numai prin WiFi</string>
<string name="time_day">zi</string>
<string name="time_left">stânga</string>
<string name="pref_bottomtoolbar">Activează bara de unelte de jos</string>
<string name="pref_bottomtoolbar_summary">Arată o bară de unelte cu acțiuni rapide din partea de jos a ecranului</string>
<string name="pref_autonightmode_summary">Schimbă automat între modul de zi și de noapte.</string>
<string name="pref_autonightmode">Mod automat de noapte</string>
<string name="pref_external_link_popup_title">Atenționați când introduceți legături extern</string>

View File

@ -94,7 +94,6 @@
<string name="zim_novid">Нет видео</string>
<string name="no_network_connection">Отсутствует сетевое подключение</string>
<string name="get_library_over_network">Используйте сеть, чтобы загрузить список контента. (Около 6MB)</string>
<string name="wait_for_load">Содержимое Ещё Загружается</string>
<string name="help_2">Что делает Kiwix?</string>
<string name="help_3">Kiwix - это автономный контент-ридер. Он действует схоже с браузером, но вместо обращения к онлайн страницам, он считывает содержимое из файла в формате ZIM.</string>
<string name="help_4">Хотя Kiwix и был изначально разработан, чтобы просматривать Wikipedia офлайн, он также пригоден и для другого содержимого.</string>
@ -138,8 +137,6 @@
<string name="time_minute">м</string>
<string name="time_second">с</string>
<string name="time_left">осталось</string>
<string name="pref_bottomtoolbar">Включить нижнюю панель управления</string>
<string name="pref_bottomtoolbar_summary">Отобразить панель управления с быстрыми действиями внизу экрана</string>
<string name="pref_autonightmode_summary">Автоматически переключать между дневным и ночным режимами.</string>
<string name="pref_autonightmode">Автоматический ночной режим</string>
<string name="pref_external_link_popup_title">Предупреждать когда происходит ввод внешних ссылок</string>

View File

@ -93,7 +93,6 @@
<string name="zim_novid">Perunu vìdeu</string>
<string name="no_network_connection">Peruna connessione de retza</string>
<string name="get_library_over_network">Imprea sa retza pro iscarrigare sa lista de cuntenutos (pagu prus o mancu 6MB)</string>
<string name="wait_for_load">Cuntenutu galu in carrigamentu</string>
<string name="help_2">Ite faghet Kiwix?</string>
<string name="help_3">Kiwix est unu leghidore de cuntenutu non in lìnia. Funtzionat comente unu navigadore ma imbetzes de intrare in pàginas web in lìnia leghet cuntenutu dae unu documentu in formatu ZIM.</string>
<string name="help_4">Fintzas si Kiwix est istadu creadu, in orìgine, pro lèghere Wikipèdia chene lìnia, leghet fintzas àteros cuntenutos.</string>
@ -135,8 +134,6 @@
<string name="time_day">die</string>
<string name="time_hour">o</string>
<string name="time_left">galu</string>
<string name="pref_bottomtoolbar">Abìlita sa barra de sas ainas in fundu</string>
<string name="pref_bottomtoolbar_summary">Ammustra una barra de sas ainas in fundu a s\'ischermu</string>
<string name="pref_autonightmode_summary">Cola in manera automàtica intre sa modalidade die e note.</string>
<string name="pref_autonightmode">Modalidade note automàtica</string>
<string name="pref_external_link_popup_title">Avisa cando ses aberende unu ligàmene esternu</string>

View File

@ -66,7 +66,6 @@
<string name="zim_simple">Enostavno</string>
<string name="zim_nopic">Ni slik</string>
<string name="zim_novid">Ni videoposnetkov</string>
<string name="wait_for_load">Vsebina se še nalaga</string>
<string name="pref_storage">Shranjevanje</string>
<string name="pref_current_folder">Trenutna mapa</string>
<string name="tts_pause">premor</string>

View File

@ -92,7 +92,6 @@
<string name="zim_novid">S\ka Video</string>
<string name="no_network_connection">S\ka lidhje rrjeti</string>
<string name="get_library_over_network">Përdor rrjetin për shkarkim liste lënde. (Afërsisht 6MB)</string>
<string name="wait_for_load">Lëndë Ende Në Ngarkim</string>
<string name="help_2">Çkryen Kiwix-i?</string>
<string name="help_3">Kiwix është një lexues lënde pa internet. Funksionon shumë ngjashëm me një shfletues, por në vend të përdorimit\n“online” të faqeve web, lëndën e lexon që nga një kartelë në formatin ZIM.</string>
<string name="help_4">Edhe pse fillimisht qe konceptuar për të lejuar përdorimin pa internet të Wikipedia-s, mund të përdoret edhe për lëndë tjetër.</string>
@ -134,8 +133,6 @@
<string name="time_day">ditë</string>
<string name="time_minute">min.</string>
<string name="time_left">edhe</string>
<string name="pref_bottomtoolbar">Aktivizo panel të poshtëm</string>
<string name="pref_bottomtoolbar_summary">Shfaq në fund të ekranit një panel me butona veprimesh të shpejta</string>
<string name="pref_autonightmode_summary">Mënyrat ditë dhe mbrëmje këmbeji automatikisht.</string>
<string name="pref_autonightmode">Mënyrë mbrëmje automatike</string>
<string name="pref_external_link_popup_title">Sinjalizo kur hyhet në lidhje të jashtme</string>

View File

@ -92,7 +92,6 @@
<string name="zim_novid">Inga videor</string>
<string name="no_network_connection">Ingen nätverksanslutning</string>
<string name="get_library_over_network">Använd nätverket för att ladda ned innehållslistan. (Uppskattningsvis 6MB)</string>
<string name="wait_for_load">Läser fortfarande in innehåll</string>
<string name="help_2">Vad gör Kiwix?</string>
<string name="help_3">Kiwix är en innehållsläsare för offlineanvändning. Den fungerar som en webbläsare, men istället för att komma åt webbsidor online läser den innehåll från en fil i ZIM-format.</string>
<string name="help_4">Även om Kiwix ursprungligen skapades för att tillhandahålla Wikipedia offline läser den även annat innehåll.</string>
@ -133,8 +132,6 @@
<string name="pref_wifi_only">Ladda ned endast via WiFi</string>
<string name="time_day">dag</string>
<string name="time_left">kvar</string>
<string name="pref_bottomtoolbar">Aktivera verktygsfält i nederkant</string>
<string name="pref_bottomtoolbar_summary">Visa ett verktygsfält med snabbåtgärder längst ned på skärmen</string>
<string name="pref_autonightmode_summary">Växla automatiskt mellan dag- och nattläge.</string>
<string name="pref_autonightmode">Automatiskt nattläge</string>
<string name="pref_external_link_popup_title">Varna vid externa länkar</string>

View File

@ -94,7 +94,6 @@
<string name="zim_novid">Video yok</string>
<string name="no_network_connection">Ağ bağlantısı yok</string>
<string name="get_library_over_network">İçerik listesini indirmek için ağı kullan. (Yaklaşık 6MB)</string>
<string name="wait_for_load">İçerik hâlâ yükleniyor</string>
<string name="help_2">Kiwix ne yapar?</string>
<string name="help_3">Kiwix bir çevrimdışı içerik okuyucusudur. Aynı bir ağ tarayıcısı gibi çalışır ama web sayfalarına çevrimiçi erişmek yerine içeriği ZIM formatındaki bir dosyadan okur.</string>
<string name="help_4">Kiwix orijinalde Vikipedi\'yi çevrimdışı olarak okuması için tasarlanmış olsa da diğer içerikleri de okuyabilir.</string>
@ -138,8 +137,6 @@
<string name="time_minute">dk</string>
<string name="time_second">sn</string>
<string name="time_left">kaldı</string>
<string name="pref_bottomtoolbar">Alt araç çubuğunu etkinleştir</string>
<string name="pref_bottomtoolbar_summary">Ekranın altındaki hızlı hareketlerle bir araç çubuğu sergileyin</string>
<string name="pref_autonightmode_summary">Gece ve gündüz modu arasında otomatik geçiş.</string>
<string name="pref_autonightmode">Otomatik gece modu</string>
<string name="pref_external_link_popup_title">Harici bağlantı girilirken uyarı ver</string>

View File

@ -94,7 +94,6 @@
<string name="zim_novid">Немає відео</string>
<string name="no_network_connection">Відсутнє з\'єднання з мережею</string>
<string name="get_library_over_network">Скористатись мережею, щоб завантажити список контенту (приблизно 6MБ)</string>
<string name="wait_for_load">Контент все ще завантажується</string>
<string name="help_2">Що робить Kiwix?</string>
<string name="help_3">Kiwix — це офлайновий читач контенту. Він багато в чому поводиться як браузер, але замість доступу до онлайнових веб-сторінок, він зчитує контент із файлу у форматі ZIM.</string>
<string name="help_4">Тоді як початково Kiwix було розроблено для забезпечення доступу до Вікіпедії в режимі офлайн, він також зчитує інші типи контенту.</string>
@ -138,8 +137,6 @@
<string name="time_minute">хв</string>
<string name="time_second">с</string>
<string name="time_left">залишилось</string>
<string name="pref_bottomtoolbar">Увімкнути нижню панель</string>
<string name="pref_bottomtoolbar_summary">Відображати знизу панель з швидкими діями</string>
<string name="pref_autonightmode_summary">Автоматично перемикатися між денним та нічним режимами.</string>
<string name="pref_autonightmode">Автоматичний нічний режим</string>
<string name="pref_external_link_popup_title">Попереджувати, коли введено зовнішнє посилання</string>

View File

@ -93,7 +93,6 @@
<string name="zim_novid">Không có video</string>
<string name="no_network_connection">Không có kết nối mạng</string>
<string name="get_library_over_network">Dùng mạng để tải về danh sách nội dung (khoảng 6MB)</string>
<string name="wait_for_load">Vẫn đang tải nội dung</string>
<string name="help_2">Kiwix dùng để làm gì?</string>
<string name="help_3">Kiwix là một chương trình đọc nội dung ngoại tuyến. Kiwix hoạt động như trình duyệt, tuy nhiên, thay vì truy cập các trang web trực tuyến thì Kiwix lại đọc nội dung từ các tập tin định dạng ZIM.</string>
<string name="help_4">Mặc dù Kiwix lúc ban đầu được tạo ra để giúp bạn truy cập Wikipedia ngoại tuyến, chương trình cũng có thể đọc các nội dung khác nữa.</string>
@ -135,8 +134,6 @@
<string name="time_minute">phút</string>
<string name="time_second">giây</string>
<string name="time_left">còn lại</string>
<string name="pref_bottomtoolbar">Bật thanh tác vụ bên dưới</string>
<string name="pref_bottomtoolbar_summary">Hiển thị một thanh tác vụ nhanh nằm ở dưới màn hình</string>
<string name="pref_autonightmode_summary">Tự động chuyển qua lại giữa chế độ đêm và ngày.</string>
<string name="pref_autonightmode">Chế độ đêm tự động</string>
<string name="pref_external_link_popup_title">Cảnh báo khi truy cập đường dẫn ngoài</string>

View File

@ -99,7 +99,6 @@
<string name="zim_novid">No Videos</string>
<string name="no_network_connection">No network connection</string>
<string name="get_library_over_network">Use network to download content list. (Approximately 6MB)</string>
<string name="wait_for_load">Content Still Loading</string>
<string name="help_2">What does Kiwix do?</string>
<string name="help_3">Kiwix is an offline content reader. It acts very much like a browser but instead of accessing online web pages, it reads content from a file in ZIM format.</string>
<string name="help_4">While Kiwix has been originally designed to provide Wikipedia offline, it also reads other contents.</string>
@ -146,12 +145,8 @@
<string name="time_left">left</string>
<string name="time_today">Today</string>
<string name="time_yesterday">Yesterday</string>
<string name="pref_bottomtoolbar">Enable bottom toolbar</string>
<string name="pref_bottomtoolbar_summary">Display a toolbar with quick actions at the bottom of the screen</string>
<string name="pref_autonightmode_summary">Automatically switch between day and night mode.</string>
<string name="pref_autonightmode">Automated night mode</string>
<string name="language_count">(%1$d)</string>
<string name="language_localized">(%s)</string>
<string name="pref_external_link_popup_title">Warn when entering external links</string>
<string name="pref_external_link_popup_summary">Display popup to warn about additional costs or not working in offline links.</string>
<string name="external_link_popup_dialog_title">Entering External Link</string>
@ -190,7 +185,6 @@
<string name="table_of_contents">Table of contents</string>
<string name="select_languages">Select languages</string>
<string name="save_languages">Save languages</string>
<string name="languages_saved">Languages saved</string>
<string name="send_feedback">Send feedback</string>
<string name="expand">Expand</string>
<string name="history">History</string>
@ -261,6 +255,10 @@
<string name="notes_deletion_none_found">No notes found for deletion</string>
<string name="notes_deletion_successful">Entire notes folder deleted</string>
<string name="notes_deletion_unsuccessful">Some files not deleted</string>
<plurals name="books_count">
<item quantity="one">%d book</item>
<item quantity="other">%d books</item>
</plurals>
<string name="discovery_initiated">Discovery initiated</string>
<string name="discovery_failed">Discovery failed</string>
<string name="severe_loss_error">Severe error! Try Disable/Re-enable WiFi P2P</string>

View File

@ -6,4 +6,6 @@
<item>large</item>
</string-array>
</resources>
<string name="slash">/</string>
<string name="plus">+</string>
</resources>

View File

@ -27,12 +27,6 @@
android:summary="@string/pref_hidetoolbar_summary"
android:title="@string/pref_hidetoolbar"/>
<org.kiwix.kiwixmobile.settings.CustomSwitchPreference
android:defaultValue="false"
android:key="pref_bottomtoolbar"
android:summary="@string/pref_bottomtoolbar_summary"
android:title="@string/pref_bottomtoolbar"/>
</PreferenceCategory>

View File

@ -21,7 +21,9 @@ import org.kiwix.kiwixmobile.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.downloader.model.DownloadState
import org.kiwix.kiwixmobile.downloader.model.DownloadState.Pending
import org.kiwix.kiwixmobile.downloader.model.DownloadStatus
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.zim_manager.Language
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import java.io.File
@ -76,3 +78,19 @@ fun downloadModel(
downloadId: Long = 1L,
book: Book = book()
) = DownloadModel(databaseId, downloadId, book)
fun language(
id: Long = 0,
isActive: Boolean = false,
occurencesOfLanguage: Int = 0,
language: String = "",
languageLocalized: String = "",
languageCode: String = "",
languageCodeISO2: String = ""
) = Language(
id, isActive, occurencesOfLanguage, language, languageLocalized, languageCode,
languageCodeISO2
)
fun languageItem(language: Language = language()) =
LanguageItem(language)

View File

@ -0,0 +1,72 @@
package org.kiwix.kiwixmobile.language.adapter
import android.view.ViewGroup
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.extensions.inflate
import org.kiwix.kiwixmobile.language.adapter.LanguageDelegate.HeaderDelegate
import org.kiwix.kiwixmobile.language.adapter.LanguageDelegate.LanguageItemDelegate
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.HeaderItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListItem.LanguageItem
import org.kiwix.kiwixmobile.language.adapter.LanguageListViewHolder.HeaderViewHolder
import org.kiwix.kiwixmobile.language.adapter.LanguageListViewHolder.LanguageViewHolder
/*
* Kiwix Android
* Copyright (C) 2018 Kiwix <android.kiwix.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
class LanguageDelegateTest {
@Nested
inner class HeaderDelegateTests {
@Test
fun `class is header item`() {
assertThat(HeaderDelegate().itemClass).isEqualTo(HeaderItem::class.java)
}
@Test
fun `creates HeaderViewHolder`() {
val parent = mockk<ViewGroup>()
mockkStatic("org.kiwix.kiwixmobile.extensions.ViewGroupExtensionsKt")
every { parent.inflate(R.layout.header_date, false) } returns mockk(relaxed = true)
assertThat(HeaderDelegate().createViewHolder(parent))
.isInstanceOf(HeaderViewHolder::class.java)
}
}
@Nested
inner class LanguageItemDelegateTests {
@Test
fun `class is lanuguage item`() {
assertThat(LanguageItemDelegate({}).itemClass).isEqualTo(LanguageItem::class.java)
}
@Test
fun `creates HeaderViewHolder`() {
val parent = mockk<ViewGroup>()
mockkStatic("org.kiwix.kiwixmobile.extensions.ViewGroupExtensionsKt")
every { parent.inflate(R.layout.item_language, false) } returns mockk(relaxed = true)
val clickAction = mockk<(LanguageItem) -> Unit>()
assertThat(LanguageItemDelegate(clickAction).createViewHolder(parent))
.isInstanceOf(LanguageViewHolder::class.java)
}
}
}

View File

@ -0,0 +1,157 @@
/*
* Kiwix Android
* Copyright (C) 2018 Kiwix <android.kiwix.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.kiwix.kiwixmobile.language.viewmodel
import com.jraska.livedata.test
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.mockk
import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.kiwix.kiwixmobile.InstantExecutorExtension
import org.kiwix.kiwixmobile.database.newdb.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.language
import org.kiwix.kiwixmobile.language.viewmodel.Action.Filter
import org.kiwix.kiwixmobile.language.viewmodel.Action.SaveAll
import org.kiwix.kiwixmobile.language.viewmodel.Action.Select
import org.kiwix.kiwixmobile.language.viewmodel.Action.UpdateLanguages
import org.kiwix.kiwixmobile.language.viewmodel.State.Content
import org.kiwix.kiwixmobile.language.viewmodel.State.Loading
import org.kiwix.kiwixmobile.language.viewmodel.State.Saving
import org.kiwix.kiwixmobile.languageItem
import org.kiwix.kiwixmobile.resetSchedulers
import org.kiwix.kiwixmobile.setScheduler
import org.kiwix.kiwixmobile.zim_manager.Language
@ExtendWith(InstantExecutorExtension::class)
class LanguageViewModelTest {
init {
setScheduler(Schedulers.trampoline())
}
@AfterAll
fun teardown() {
resetSchedulers()
}
private val newLanguagesDao: NewLanguagesDao = mockk()
private lateinit var languageViewModel: LanguageViewModel
private val languages: PublishProcessor<List<Language>> = PublishProcessor.create()
@BeforeEach
fun init() {
clearAllMocks()
every { newLanguagesDao.languages() } returns languages
languageViewModel = LanguageViewModel(newLanguagesDao)
}
@Test
fun `initial state is Loading`() {
languageViewModel.state.test()
.assertValueHistory(Loading)
}
@Test
fun `an empty languages emission does not send update action`() {
languageViewModel.actions.test()
.also {
languages.offer(listOf())
}
.assertValues()
}
@Test
fun `a languages emission sends update action`() {
val expectedList = listOf(language())
languageViewModel.actions.test()
.also {
languages.offer(expectedList)
}
.assertValues(UpdateLanguages(expectedList))
}
@Test
fun `UpdateLanguages Action changes state to Content when Loading`() {
languageViewModel.actions.offer(UpdateLanguages(listOf()))
languageViewModel.state.test()
.assertValueHistory(Content(listOf()))
}
@Test
fun `UpdateLanguages Action has no effect on other states`() {
languageViewModel.actions.offer(UpdateLanguages(listOf()))
languageViewModel.actions.offer(UpdateLanguages(listOf()))
languageViewModel.state.test()
.assertValueHistory(Content(listOf()))
}
@Test
fun `Filter Action updates Content state `() {
languageViewModel.actions.offer(UpdateLanguages(listOf()))
languageViewModel.actions.offer(Filter("filter"))
languageViewModel.state.test()
.assertValueHistory(Content(listOf(), filter = "filter"))
}
@Test
fun `Filter Action has no effect on other states`() {
languageViewModel.actions.offer(Filter(""))
languageViewModel.state.test()
.assertValueHistory(Loading)
}
@Test
fun `Select Action updates Content state`() {
languageViewModel.actions.offer(UpdateLanguages(listOf(language())))
languageViewModel.actions.offer(Select(languageItem()))
languageViewModel.state.test()
.assertValueHistory(Content(listOf(language(isActive = true))))
}
@Test
fun `Select Action has no effect on other states`() {
languageViewModel.actions.offer(Select(languageItem()))
languageViewModel.state.test()
.assertValueHistory(Loading)
}
@Test
fun `SaveAll changes Content to Saving with SideEffect SaveLanguagesAndFinish`() {
languageViewModel.actions.offer(UpdateLanguages(listOf()))
languageViewModel.effects.test()
.also {
languageViewModel.actions.offer(SaveAll)
}
.assertValues(SaveLanguagesAndFinish(listOf(), newLanguagesDao))
languageViewModel.state.test()
.assertValueHistory(Saving)
}
@Test
fun `SaveAll has no effect on other states`() {
languageViewModel.actions.offer(SaveAll)
languageViewModel.state.test()
.assertValueHistory(Loading)
}
}

Some files were not shown because too many files have changed in this diff Show More