Merge remote-tracking branch 'origin/develop' into feature/macgills/#1214-nightly-and-release-uploads

This commit is contained in:
Sean Mac Gillicuddy 2019-07-09 10:06:45 +01:00
commit 292d26da73
38 changed files with 17772 additions and 474 deletions

View File

@ -226,20 +226,14 @@ android {
testBuildType "debug"
lintOptions {
// Treat lint seriously
abortOnError true
// Hardcoded strings in xml
error "HardcodedText"
// Hardcoded strings in setText
error "SetTextI18n"
// Strings.xml issues
warning "ExtraTranslation"
warning "MissingTranslation"
// Warnings
warning "InvalidPackage"
warning "StringFormatInvalid"
checkAllWarnings true
warningsAsErrors true
ignore 'MissingTranslation', //TODO stop ignoring
'CheckResult'
baseline file("lint-baseline.xml")
}
testOptions {
unitTests.returnDefaultValues = true
unitTests.all {

16748
app/lint-baseline.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -218,7 +218,7 @@
},
{
"id": "2:6862771806221961183",
"name": "zimID"
"name": "zimId"
},
{
"id": "3:4312769031500860715",

View File

@ -23,6 +23,7 @@ import dagger.Subcomponent
import org.kiwix.kiwixmobile.di.modules.ActivityModule
import org.kiwix.kiwixmobile.downloader.DownloadFragment
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.ZimFileSelectFragment
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.DeleteFiles
import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment
@Subcomponent(modules = [ActivityModule::class])
@ -33,6 +34,8 @@ interface ActivityComponent {
fun inject(zimFileSelectFragment: ZimFileSelectFragment)
fun inject(deleteFiles: DeleteFiles)
@Subcomponent.Builder
interface Builder {

View File

@ -18,10 +18,13 @@
package org.kiwix.kiwixmobile.downloader.model
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.utils.StorageUtils
data class DownloadModel(
val databaseId: Long? = null,
val downloadId: Long ,
val downloadId: Long,
val book: Book
)
) {
val fileNameFromUrl: String get() = StorageUtils.getFileNameFromUrl(book.url)
}

View File

@ -0,0 +1,43 @@
package org.kiwix.kiwixmobile.extensions
import android.app.Activity
import android.view.ActionMode
import android.view.ActionMode.Callback
import android.view.Menu
import android.view.MenuItem
fun Activity.startActionMode(
menuId: Int,
idsToClickActions: Map<Int, () -> Any>,
onDestroyAction: () -> Unit
): ActionMode? {
return startActionMode(object : Callback {
override fun onActionItemClicked(
mode: ActionMode,
item: MenuItem
) = idsToClickActions[item.itemId]?.let {
it()
mode.finish()
true
} ?: false
override fun onCreateActionMode(
mode: ActionMode,
menu: Menu?
): Boolean {
mode.getMenuInflater()
.inflate(menuId, menu)
return true
}
override fun onPrepareActionMode(
mode: ActionMode?,
menu: Menu?
) = false
override fun onDestroyActionMode(mode: ActionMode?) {
onDestroyAction()
}
})
}

View File

@ -37,7 +37,6 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Environment;
import android.os.Handler;
import android.provider.Settings;
import android.text.SpannableString;
@ -84,10 +83,10 @@ import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import io.reactivex.android.schedulers.AndroidSchedulers;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
@ -100,20 +99,18 @@ import org.kiwix.kiwixmobile.base.BaseActivity;
import org.kiwix.kiwixmobile.bookmark.BookmarkItem;
import org.kiwix.kiwixmobile.bookmark.BookmarksActivity;
import org.kiwix.kiwixmobile.data.ZimContentProvider;
import org.kiwix.kiwixmobile.data.local.entity.Bookmark;
import org.kiwix.kiwixmobile.help.HelpActivity;
import org.kiwix.kiwixmobile.history.HistoryActivity;
import org.kiwix.kiwixmobile.history.HistoryListItem;
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity;
import org.kiwix.kiwixmobile.search.SearchActivity;
import org.kiwix.kiwixmobile.settings.KiwixSettingsActivity;
import org.kiwix.kiwixmobile.utils.DimenUtils;
import org.kiwix.kiwixmobile.utils.LanguageUtils;
import org.kiwix.kiwixmobile.utils.NetworkUtils;
import org.kiwix.kiwixmobile.utils.StyleUtils;
import org.kiwix.kiwixmobile.utils.files.FileSearch;
import org.kiwix.kiwixmobile.utils.files.FileUtils;
import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity;
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.StorageObserver;
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BookOnDiskDelegate;
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskAdapter;
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem;
@ -201,14 +198,18 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
ImageView bottomToolbarArrowBack;
@BindView(R.id.bottom_toolbar_arrow_forward)
ImageView bottomToolbarArrowForward;
@Inject
MainContract.Presenter presenter;
@BindView(R.id.tab_switcher_recycler_view)
RecyclerView tabRecyclerView;
@BindView(R.id.activity_main_tab_switcher)
View tabSwitcherRoot;
@BindView(R.id.tab_switcher_close_all_tabs)
FloatingActionButton closeAllTabsButton;
@Inject
MainContract.Presenter presenter;
@Inject
StorageObserver storageObserver;
private CountDownTimer hideBackToTopTimer = new CountDownTimer(1200, 1200) {
@Override
public void onTick(long millisUntilFinished) {
@ -274,21 +275,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
closeTab(viewHolder.getAdapterPosition());
}
};
private FileSearch fileSearch =
new FileSearch(this, Collections.emptyList(), new FileSearch.ResultListener() {
final List<BooksOnDiskListItem.BookOnDisk> newBooks = new ArrayList<>();
@Override public void onBookFound(BooksOnDiskListItem.BookOnDisk bookOnDisk) {
runOnUiThread(() -> {
newBooks.add(bookOnDisk);
});
}
@Override
public void onScanCompleted() {
presenter.saveBooks(newBooks);
}
});
private static void updateWidgets(Context context) {
Intent intent = new Intent(context.getApplicationContext(), KiwixSearchWidget.class);
@ -377,6 +364,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
open(bookOnDiskItem);
return Unit.INSTANCE;
},
null,
null),
BookOnDiskDelegate.LanguageDelegate.INSTANCE
);
@ -739,7 +727,6 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
downloadBookButton = null;
hideBackToTopTimer.cancel();
hideBackToTopTimer = null;
fileSearch = null;
// TODO create a base Activity class that class this.
FileUtils.deleteCachedFiles(this);
tts.shutdown();
@ -1199,7 +1186,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
case REQUEST_READ_STORAGE_PERMISSION: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
fileSearch.scan(sharedPreferenceUtil.getPrefStorage());
scanStorageForZims();
} else {
Snackbar.make(drawerLayout, R.string.request_storage, Snackbar.LENGTH_LONG)
.setAction(R.string.menu_settings, view -> {
@ -1228,6 +1215,13 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
}
}
private void scanStorageForZims() {
storageObserver.getBooksOnFileSystem()
.take(1)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(presenter::saveBooks, Throwable::printStackTrace);
}
// Workaround for popup bottom menu on older devices
private void StyleMenuButtons(Menu m) {
// Find each menu item and set its text colour
@ -2105,7 +2099,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
@Override
public void addBooks(List<BooksOnDiskListItem> books) {
booksAdapter.setItemList(books);
booksAdapter.setItems(books);
}
private void searchFiles() {
@ -2116,7 +2110,7 @@ public class MainActivity extends BaseActivity implements WebViewCallback,
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
REQUEST_READ_STORAGE_PERMISSION);
} else {
fileSearch.scan(sharedPreferenceUtil.getPrefStorage());
scanStorageForZims();
}
}

View File

@ -1,296 +0,0 @@
/*
* Copyright 2013 Rashiq Ahmad <rashiq.z@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 org.kiwix.kiwixmobile.utils.files;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import eu.mhutti1.utils.storage.StorageDevice;
import eu.mhutti1.utils.storage.StorageDeviceUtils;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Vector;
import org.kiwix.kiwixmobile.data.ZimContentProvider;
import org.kiwix.kiwixmobile.downloader.model.DownloadModel;
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity;
import org.kiwix.kiwixmobile.utils.StorageUtils;
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem;
import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX;
public class FileSearch {
// Array of zim file extensions
public static final String[] zimFiles = { "zim", "zimaa" };
private final Context context;
private final List<DownloadModel> downloads;
private final ResultListener listener;
private boolean fileSystemScanCompleted = false;
private boolean mediaStoreScanCompleted = false;
public FileSearch(Context ctx, List<DownloadModel> downloads, ResultListener listener) {
this.context = ctx;
this.downloads = downloads;
this.listener = listener;
}
public static synchronized LibraryNetworkEntity.Book fileToBook(String filePath) {
LibraryNetworkEntity.Book book = null;
if (ZimContentProvider.zimFileName != null) {
ZimContentProvider.originalFileName = ZimContentProvider.zimFileName;
}
// Check a file isn't being opened and temporally use content provider to access details
// This is not a great solution as we shouldn't need to fully open our ZIM files to get their metadata
if (ZimContentProvider.canIterate) {
if (ZimContentProvider.setZimFile(filePath) != null) {
try {
book = new LibraryNetworkEntity.Book();
book.title = ZimContentProvider.getZimFileTitle();
book.id = ZimContentProvider.getId();
book.file = new File(filePath);
book.size = String.valueOf(ZimContentProvider.getFileSize());
book.favicon = ZimContentProvider.getFavicon();
book.creator = ZimContentProvider.getCreator();
book.publisher = ZimContentProvider.getPublisher();
book.date = ZimContentProvider.getDate();
book.description = ZimContentProvider.getDescription();
book.language = ZimContentProvider.getLanguage();
} catch (Exception e) {
// TODO 20171215 Consider more elegant approaches.
// This is to see if we can catch the exception at all!
Log.e("kiwix-filesearch", "Problem parsing a book entry from the library file. ", e);
return null;
}
}
}
// Return content provider to its previous state
if (!ZimContentProvider.originalFileName.equals("")) {
ZimContentProvider.setZimFile(ZimContentProvider.originalFileName);
}
ZimContentProvider.originalFileName = "";
return book;
}
public void scan(String defaultPath) {
// Start custom file search
new Thread(() -> {
scanFileSystem(defaultPath);
fileSystemScanCompleted = true;
checkCompleted();
}).start();
// Star mediastore search
new Thread(() -> {
scanMediaStore();
mediaStoreScanCompleted = true;
checkCompleted();
}).start();
}
// If both searches are complete callback
private synchronized void checkCompleted() {
if (mediaStoreScanCompleted && fileSystemScanCompleted) {
listener.onScanCompleted();
}
}
public void scanMediaStore() {
ContentResolver contentResolver = context.getContentResolver();
Uri uri = MediaStore.Files.getContentUri("external");
String[] projection = { MediaStore.MediaColumns.DATA };
String selection =
MediaStore.MediaColumns.DATA + " like ? or " + MediaStore.MediaColumns.DATA + " like ? ";
Cursor query = contentResolver.query(uri, projection, selection,
new String[] { "%." + zimFiles[0], "%." + zimFiles[1] }, null);
if (query == null) {
return;
}
try {
while (query.moveToNext()) {
File file = new File(query.getString(0));
if (file.canRead()) {
onFileFound(file.getAbsolutePath());
}
}
} finally {
query.close();
}
}
// Scan through the file system and find all the files with .zim and .zimaa extensions
public void scanFileSystem(String defaultPath) {
FilenameFilter[] filter = new FilenameFilter[zimFiles.length];
// Search all external directories that we can find.
final ArrayList<StorageDevice> storageDevices =
StorageDeviceUtils.getStorageDevices(context, false);
String[] tempRoots = new String[storageDevices.size() + 2];
int j = 0;
tempRoots[j++] = "/mnt";
tempRoots[j++] = defaultPath;
for (StorageDevice storageDevice : storageDevices) {
tempRoots[j++] = storageDevice.getName();
}
int i = 0;
for (final String extension : zimFiles) {
filter[i] = (dir, name) -> name.endsWith("." + extension);
i++;
}
String dirNamePrimary = new File(
Environment.getExternalStorageDirectory().getAbsolutePath()).toString();
for (final String dirName : tempRoots) {
if (dirNamePrimary.equals(dirName)) {
// We already got this directory from getExternalStorageDirectory().
continue;
}
File f = new File(dirName);
if (f.isDirectory()) {
scanDirectory(dirName, filter);
} else {
Log.i(TAG_KIWIX, "Skipping missing directory " + dirName);
}
}
}
// Iterate through the file system
private Collection<File> listFiles(File directory, FilenameFilter[] filter, int recurse) {
Vector<File> files = new Vector<>();
File[] entries = directory.listFiles();
if (entries != null) {
for (File entry : entries) {
for (FilenameFilter filefilter : filter) {
if (filter == null || filefilter.accept(directory, entry.getName())) {
files.add(entry);
}
}
if ((recurse <= -1) || (recurse > 0 && entry.isDirectory())) {
recurse--;
files.addAll(listFiles(entry, filter, recurse));
recurse++;
}
}
}
return files;
}
private File[] listFilesAsArray(File directory, FilenameFilter[] filter, int recurse) {
Collection<File> files = listFiles(directory, filter, recurse);
File[] arr = new File[files.size()];
return files.toArray(arr);
}
public static synchronized BooksOnDiskListItem.BookOnDisk fileToBookOnDisk(String filePath) {
LibraryNetworkEntity.Book book = null;
if (ZimContentProvider.zimFileName != null) {
ZimContentProvider.originalFileName = ZimContentProvider.zimFileName;
}
// Check a file isn't being opened and temporally use content provider to access details
// This is not a great solution as we shouldn't need to fully open our ZIM files to get their metadata
if (ZimContentProvider.canIterate) {
if (ZimContentProvider.setZimFile(filePath) != null) {
try {
book = new LibraryNetworkEntity.Book();
book.title = ZimContentProvider.getZimFileTitle();
book.id = ZimContentProvider.getId();
book.size = String.valueOf(ZimContentProvider.getFileSize());
book.favicon = ZimContentProvider.getFavicon();
book.creator = ZimContentProvider.getCreator();
book.publisher = ZimContentProvider.getPublisher();
book.date = ZimContentProvider.getDate();
book.description = ZimContentProvider.getDescription();
book.language = ZimContentProvider.getLanguage();
} catch (Exception e) {
// TODO 20171215 Consider more elegant approaches.
// This is to see if we can catch the exception at all!
Log.e("kiwix-filesearch", "Problem parsing a book entry from the library file. ", e);
return null;
}
}
}
// Return content provider to its previous state
if (!ZimContentProvider.originalFileName.equals("")) {
ZimContentProvider.setZimFile(ZimContentProvider.originalFileName);
}
ZimContentProvider.originalFileName = "";
return book == null ? null
: new BooksOnDiskListItem.BookOnDisk(null, book, new File(filePath),0L);
}
// Fill fileList with files found in the specific directory
private void scanDirectory(String directory, FilenameFilter[] filter) {
Log.d(TAG_KIWIX, "Searching directory " + directory);
File[] foundFiles = listFilesAsArray(new File(directory), filter, -1);
for (File f : foundFiles) {
Log.d(TAG_KIWIX, "Found " + f.getAbsolutePath());
onFileFound(f.getAbsolutePath());
}
}
// Callback that a new file has been found
public void onFileFound(String filePath) {
if (fileIsDownloading(filePath)) {
return;
}
BooksOnDiskListItem.BookOnDisk book = fileToBookOnDisk(filePath);
if (book != null) {
listener.onBookFound(book);
}
}
private boolean fileIsDownloading(String filePath) {
for (DownloadModel download : downloads) {
if (filePath.endsWith(StorageUtils.getFileNameFromUrl(download.getBook().getUrl()))) {
return true;
}
}
return false;
}
public interface ResultListener {
void onBookFound(BooksOnDiskListItem.BookOnDisk book);
void onScanCompleted();
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright 2013 Rashiq Ahmad <rashiq.z@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 org.kiwix.kiwixmobile.utils.files
import android.content.Context
import android.os.Environment
import android.provider.MediaStore.Files
import android.provider.MediaStore.MediaColumns
import eu.mhutti1.utils.storage.StorageDeviceUtils
import io.reactivex.Flowable
import io.reactivex.functions.BiFunction
import org.kiwix.kiwixmobile.extensions.forEachRow
import org.kiwix.kiwixmobile.extensions.get
import java.io.File
import javax.inject.Inject
class FileSearch @Inject constructor(private val context: Context) {
val zimFileExtensions = arrayOf("zim", "zimaa")
fun scan(defaultPath: String) =
Flowable.combineLatest(
Flowable.fromCallable { scanFileSystem(defaultPath) },
Flowable.fromCallable(this::scanMediaStore),
BiFunction<List<File>, List<File>, List<File>> { filesSystemFiles, mediaStoreFiles ->
filesSystemFiles + mediaStoreFiles
}
)
private fun scanMediaStore() = mutableListOf<File>().apply {
queryMediaStore()
?.forEachRow { cursor ->
File(cursor.get<String>(MediaColumns.DATA)).takeIf(File::canRead)
?.also { add(it) }
}
}
private fun queryMediaStore() = context.contentResolver
.query(
Files.getContentUri("external"),
arrayOf(MediaColumns.DATA),
MediaColumns.DATA + " like ? or " + MediaColumns.DATA + " like ? ",
arrayOf("%." + zimFileExtensions[0], "%." + zimFileExtensions[1]),
null
)
private fun scanFileSystem(defaultPath: String) =
directoryRoots(defaultPath)
.minus(Environment.getExternalStorageDirectory().absolutePath)
.fold(mutableListOf<File>(), { acc, root ->
acc.apply { addAll(scanDirectory(root)) }
})
private fun directoryRoots(defaultPath: String) = listOf(
"/mnt",
defaultPath,
*StorageDeviceUtils.getStorageDevices(context, false).map { it.name }.toTypedArray()
)
private fun scanDirectory(directory: String) = filesMatchingExtensions(directory) ?: emptyList()
private fun filesMatchingExtensions(directory: String) = File(directory)
.listFiles { _, name -> name.endsWithAny(*zimFileExtensions) }
?.toList()
}
internal fun String.endsWithAny(vararg suffixes: String) =
suffixes.fold(false, { acc, s -> acc or endsWith(s) })

View File

@ -52,9 +52,24 @@ import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CanWrite4G
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CannotWrite4GbFile
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.NotEnoughSpaceFor4GbFile
import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.MultiModeFinished
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestDeleteMultiSelection
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestMultiSelection
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestOpen
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestSelect
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestShareMultiSelection
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.FileSelectListState
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode.MULTI
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode.NORMAL
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.StorageObserver
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.DeleteFiles
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.None
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.OpenFile
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.ShareFiles
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.SideEffect
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects.StartMultiSelection
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.DividerItem
@ -79,16 +94,26 @@ class ZimManageViewModel @Inject constructor(
private val defaultLanguageProvider: DefaultLanguageProvider,
private val dataSource: DataSource
) : ViewModel() {
sealed class FileSelectActions {
data class RequestOpen(val bookOnDisk: BookOnDisk) : FileSelectActions()
data class RequestSelect(val bookOnDisk: BookOnDisk) : FileSelectActions()
data class RequestMultiSelection(val bookOnDisk: BookOnDisk) : FileSelectActions()
object RequestDeleteMultiSelection : FileSelectActions()
object RequestShareMultiSelection : FileSelectActions()
object MultiModeFinished : FileSelectActions()
}
val sideEffects = PublishProcessor.create<SideEffect<out Any?>>()
val libraryItems: MutableLiveData<List<LibraryListItem>> = MutableLiveData()
val downloadItems: MutableLiveData<List<DownloadItem>> = MutableLiveData()
val bookItems: MutableLiveData<List<BooksOnDiskListItem>> = MutableLiveData()
val fileSelectListStates: MutableLiveData<FileSelectListState> = MutableLiveData()
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>()
@ -127,10 +152,74 @@ class ZimManageViewModel @Inject constructor(
updateLanguagesInDao(networkLibrary, languages),
updateNetworkStates(),
updateLanguageItemsForDialog(languages),
requestsAndConnectivtyChangesToLibraryRequests(networkLibrary)
requestsAndConnectivtyChangesToLibraryRequests(networkLibrary),
fileSelectActions()
)
}
private fun fileSelectActions() = fileSelectActions.subscribe({
sideEffects.offer(
when (it) {
is RequestOpen -> OpenFile(it.bookOnDisk)
is RequestMultiSelection -> startMultiSelectionAndSelectBook(it.bookOnDisk)
RequestDeleteMultiSelection -> DeleteFiles(selectionsFromState())
RequestShareMultiSelection -> ShareFiles(selectionsFromState())
MultiModeFinished -> noSideEffectAndClearSelectionState()
is RequestSelect -> noSideEffectSelectBook(it.bookOnDisk)
}
)
}, Throwable::printStackTrace)
private fun startMultiSelectionAndSelectBook(
bookOnDisk: BookOnDisk
): StartMultiSelection {
fileSelectListStates.value?.let {
fileSelectListStates.postValue(
it.copy(
bookOnDiskListItems = selectBook(it, bookOnDisk),
selectionMode = MULTI
)
)
}
return StartMultiSelection(bookOnDisk, fileSelectActions)
}
private fun selectBook(
it: FileSelectListState,
bookOnDisk: BookOnDisk
): List<BooksOnDiskListItem> {
return it.bookOnDiskListItems.map { listItem ->
if (listItem.id == bookOnDisk.id) listItem.apply { isSelected = !isSelected }
else listItem
}
}
private fun noSideEffectSelectBook(bookOnDisk: BookOnDisk): SideEffect<Unit> {
fileSelectListStates.value?.let {
fileSelectListStates.postValue(
it.copy(bookOnDiskListItems = it.bookOnDiskListItems.map { listItem ->
if (listItem.id == bookOnDisk.id) listItem.apply { isSelected = !isSelected }
else listItem
})
)
}
return None
}
private fun selectionsFromState() = fileSelectListStates.value?.selectedBooks ?: emptyList()
private fun noSideEffectAndClearSelectionState(): SideEffect<Unit> {
fileSelectListStates.value?.let {
fileSelectListStates.postValue(
it.copy(
bookOnDiskListItems = it.bookOnDiskListItems.map { it.apply { isSelected = false } },
selectionMode = NORMAL
)
)
}
return None
}
private fun requestsAndConnectivtyChangesToLibraryRequests(library: PublishProcessor<LibraryNetworkEntity>) =
Flowable.combineLatest(
requestDownloadLibrary,
@ -411,10 +500,27 @@ class ZimManageViewModel @Inject constructor(
private fun updateBookItems() =
dataSource.booksOnDiskAsListItems()
.subscribe(
bookItems::postValue,
{ newList ->
fileSelectListStates.postValue(
fileSelectListStates.value?.let { inheritSelections(it, newList) }
?: FileSelectListState(newList)
)
},
Throwable::printStackTrace
)
private fun inheritSelections(
oldState: FileSelectListState,
newList: MutableList<BooksOnDiskListItem>
): FileSelectListState {
return oldState.copy(
bookOnDiskListItems = newList.map { newBookOnDisk ->
val firstOrNull =
oldState.bookOnDiskListItems.firstOrNull { oldBookOnDisk -> oldBookOnDisk.id == newBookOnDisk.id }
newBookOnDisk.apply { isSelected = firstOrNull?.isSelected ?: false }
})
}
private fun removeCompletedDownloadsFromDb(downloadStatuses: Flowable<List<DownloadStatus>>) =
downloadStatuses
.observeOn(Schedulers.io())

View File

@ -0,0 +1,37 @@
/*
* 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.zim_manager.fileselect_view
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode.NORMAL
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
data class FileSelectListState(
val bookOnDiskListItems: List<BooksOnDiskListItem>,
val selectionMode: SelectionMode = NORMAL
) {
val selectedBooks by lazy {
bookOnDiskListItems.filter { it.isSelected }.filterIsInstance(BookOnDisk::class.java)
}
}
enum class SelectionMode {
NORMAL,
MULTI
}

View File

@ -1,45 +1,80 @@
package org.kiwix.kiwixmobile.zim_manager.fileselect_view
import android.content.Context
import android.util.Log
import io.reactivex.processors.PublishProcessor
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import org.kiwix.kiwixmobile.data.ZimContentProvider
import org.kiwix.kiwixmobile.database.newdb.dao.NewDownloadDao
import org.kiwix.kiwixmobile.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.utils.files.FileSearch
import org.kiwix.kiwixmobile.utils.files.FileSearch.ResultListener
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import java.io.File
import javax.inject.Inject
class StorageObserver @Inject constructor(
private val context: Context,
private val sharedPreferenceUtil: SharedPreferenceUtil,
private val downloadDao: NewDownloadDao
downloadDao: NewDownloadDao,
private val fileSearch: FileSearch
) {
private val _booksOnFileSystem = PublishProcessor.create<List<BookOnDisk>>()
val booksOnFileSystem = _booksOnFileSystem.distinctUntilChanged()
.doOnSubscribe {
downloadDao.downloads()
.subscribeOn(Schedulers.io())
.take(1)
.subscribe(this::scanFiles, Throwable::printStackTrace)
val booksOnFileSystem = scanFiles()
.withLatestFrom(
downloadDao.downloads(),
BiFunction(this::toFilesThatAreNotDownloading)
)
.map {
it.mapNotNull { file -> convertToBookOnDisk(file) }
}
private fun scanFiles(downloads: List<DownloadModel>) {
FileSearch(context, downloads, object : ResultListener {
val foundBooks = mutableSetOf<BookOnDisk>()
private fun toFilesThatAreNotDownloading(
files: List<File>,
downloads: List<DownloadModel>
) = files.filter { fileHasNoMatchingDownload(downloads, it) }
override fun onBookFound(book: BookOnDisk) {
foundBooks.add(book)
Log.i("Scanner", "File Search: Found Book " + book.book.title)
}
private fun fileHasNoMatchingDownload(
downloads: List<DownloadModel>,
file: File
) = downloads.firstOrNull {
file.absolutePath.endsWith(it.fileNameFromUrl)
} == null
override fun onScanCompleted() {
_booksOnFileSystem.onNext(foundBooks.toList())
private fun scanFiles() = fileSearch.scan(sharedPreferenceUtil.prefStorage)
.subscribeOn(Schedulers.io())
}
}).scan(sharedPreferenceUtil.prefStorage)
private fun convertToBookOnDisk(file: File): BookOnDisk? {
configureZimContentProvider()
var bookOnDisk: BookOnDisk? = null
if (ZimContentProvider.canIterate && ZimContentProvider.setZimFile(file.absolutePath) != null) {
bookOnDisk = BookOnDisk(book = bookFromZimContentProvider(), file = file)
}
resetZimContentProvider()
return bookOnDisk
}
private fun bookFromZimContentProvider() = Book().apply {
title = ZimContentProvider.getZimFileTitle()
id = ZimContentProvider.getId()
size = ZimContentProvider.getFileSize()
.toString()
favicon = ZimContentProvider.getFavicon()
creator = ZimContentProvider.getCreator()
publisher = ZimContentProvider.getPublisher()
date = ZimContentProvider.getDate()
description = ZimContentProvider.getDescription()
language = ZimContentProvider.getLanguage()
}
private fun configureZimContentProvider() {
if (ZimContentProvider.zimFileName != null) {
ZimContentProvider.originalFileName = ZimContentProvider.zimFileName
}
}
private fun resetZimContentProvider() {
if (ZimContentProvider.originalFileName != "") {
ZimContentProvider.setZimFile(ZimContentProvider.originalFileName)
}
ZimContentProvider.originalFileName = ""
}
}

View File

@ -21,8 +21,8 @@ package org.kiwix.kiwixmobile.zim_manager.fileselect_view
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.view.ActionMode
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -32,49 +32,49 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.zim_list.file_management_no_files
import kotlinx.android.synthetic.main.zim_list.zim_swiperefresh
import kotlinx.android.synthetic.main.zim_list.zimfilelist
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.R.string
import org.kiwix.kiwixmobile.base.BaseFragment
import org.kiwix.kiwixmobile.data.ZimContentProvider
import org.kiwix.kiwixmobile.database.newdb.dao.NewBookDao
import org.kiwix.kiwixmobile.di.components.ActivityComponent
import org.kiwix.kiwixmobile.extensions.toast
import org.kiwix.kiwixmobile.utils.BookUtils
import org.kiwix.kiwixmobile.utils.Constants.REQUEST_STORAGE_PERMISSION
import org.kiwix.kiwixmobile.utils.DialogShower
import org.kiwix.kiwixmobile.utils.KiwixDialog.DeleteZim
import org.kiwix.kiwixmobile.utils.LanguageUtils
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.utils.files.FileUtils
import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestMultiSelection
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestOpen
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestSelect
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BookOnDiskDelegate.BookDelegate
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BookOnDiskDelegate.LanguageDelegate
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskAdapter
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import javax.inject.Inject
class ZimFileSelectFragment : BaseFragment() {
@Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil
@Inject lateinit var bookDao: NewBookDao
@Inject lateinit var dialogShower: DialogShower
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
@Inject lateinit var bookUtils: BookUtils
private var actionMode: ActionMode? = null
val disposable = CompositeDisposable()
private val zimManageViewModel: ZimManageViewModel by lazy {
ViewModelProviders.of(activity!!, viewModelFactory)
.get(ZimManageViewModel::class.java)
}
private val bookDelegate: BookDelegate by lazy {
BookDelegate(sharedPreferenceUtil,
{ offerAction(RequestOpen(it)) },
{ offerAction(RequestMultiSelection(it)) },
{ offerAction(RequestSelect(it)) })
}
private val booksOnDiskAdapter: BooksOnDiskAdapter by lazy {
BooksOnDiskAdapter(
BookDelegate(sharedPreferenceUtil, this::openOnClick, this::deleteOnLongClick),
LanguageDelegate
)
BooksOnDiskAdapter(bookDelegate, LanguageDelegate)
}
override fun inject(activityComponent: ActivityComponent) {
@ -101,31 +101,51 @@ class ZimFileSelectFragment : BaseFragment() {
layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
setHasFixedSize(true)
}
zimManageViewModel.bookItems.observe(this, Observer {
booksOnDiskAdapter.itemList = it!!
checkEmpty(it)
})
zimManageViewModel.fileSelectListStates.observe(this, Observer { render(it) })
disposable.add(sideEffects())
zimManageViewModel.deviceListIsRefreshing.observe(this, Observer {
zim_swiperefresh.isRefreshing = it!!
})
}
private fun sideEffects() = zimManageViewModel.sideEffects.subscribe(
{
val effectResult = it.invokeWith(activity!!)
if (effectResult is ActionMode) {
actionMode = effectResult
}
}, Throwable::printStackTrace
)
private fun render(state: FileSelectListState) {
val items = state.bookOnDiskListItems
bookDelegate.selectionMode = state.selectionMode
booksOnDiskAdapter.items = items
actionMode?.title = String.format("%d", state.selectedBooks.size)
file_management_no_files.visibility = if (items.isEmpty()) View.VISIBLE else View.GONE
}
override fun onResume() {
super.onResume()
checkPermissions()
}
private fun checkEmpty(books: List<Any>) {
file_management_no_files.visibility =
if (books.isEmpty()) View.VISIBLE
else View.GONE
override fun onDestroy() {
super.onDestroy()
disposable.clear()
}
private fun offerAction(
action: FileSelectActions
) {
zimManageViewModel.fileSelectActions.offer(action)
}
private fun checkPermissions() {
if (ContextCompat.checkSelfPermission(
activity!!,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) != PackageManager.PERMISSION_GRANTED && Build.VERSION.SDK_INT > 18
) != PackageManager.PERMISSION_GRANTED
) {
context.toast(R.string.request_storage)
requestPermissions(
@ -140,34 +160,4 @@ class ZimFileSelectFragment : BaseFragment() {
private fun requestFileSystemCheck() {
zimManageViewModel.requestFileSystemCheck.onNext(Unit)
}
private fun openOnClick(it: BookOnDisk) {
val file = it.file
ZimContentProvider.canIterate = false
if (!file.canRead()) {
context.toast(string.error_filenotfound)
} else {
(activity as ZimManageActivity).finishResult(file.path)
}
}
private fun deleteOnLongClick(it: BookOnDisk) {
dialogShower.show(DeleteZim, {
if (deleteSpecificZimFile(it)) {
context.toast(string.delete_specific_zim_toast)
} else {
context.toast(string.delete_zim_failed)
}
})
}
private fun deleteSpecificZimFile(book: BookOnDisk): Boolean {
val file = book.file
FileUtils.deleteZimFile(file.path)
if (file.exists()) {
return false
}
bookDao.delete(book.databaseId!!)
return true
}
}

View File

@ -18,11 +18,13 @@
package org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.extensions.inflate
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode.NORMAL
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BookOnDiskViewHolder.BookViewHolder
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BookOnDiskViewHolder.LanguageItemViewHolder
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.LanguageItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.AbsDelegateAdapter
@ -33,17 +35,28 @@ sealed class BookOnDiskDelegate<I : BooksOnDiskListItem, VH : BookOnDiskViewHold
class BookDelegate(
val sharedPreferenceUtil: SharedPreferenceUtil,
val clickAction: (BookOnDisk) -> Unit,
val longClickAction: ((BookOnDisk) -> Unit)? = null
val longClickAction: ((BookOnDisk) -> Unit)? = null,
val multiSelectAction: ((BookOnDisk) -> Unit)? = null
) : BookOnDiskDelegate<BookOnDisk, BookViewHolder>() {
override val itemClass = BookOnDisk::class.java
var selectionMode: SelectionMode = NORMAL
override fun bind(
viewHolder: ViewHolder,
itemToBind: BooksOnDiskListItem
) {
(viewHolder as BookOnDiskViewHolder.BookViewHolder).bind((itemToBind as BookOnDisk), selectionMode)
}
override fun createViewHolder(parent: ViewGroup) =
BookViewHolder(
parent.inflate(R.layout.item_book, false),
sharedPreferenceUtil,
clickAction,
longClickAction
longClickAction,
multiSelectAction
)
}

View File

@ -1,7 +1,6 @@
package org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.AdapterDelegate
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.AdapterDelegateManager
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.BaseDelegateAdapter
class BooksOnDiskAdapter(

View File

@ -6,6 +6,7 @@ import java.io.File
import java.util.Locale
sealed class BooksOnDiskListItem {
var isSelected: Boolean = false
abstract val id: Long
data class LanguageItem constructor(

View File

@ -3,6 +3,7 @@ package org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter
import android.graphics.ColorMatrixColorFilter
import android.view.View
import kotlinx.android.synthetic.main.header_language.header_language
import kotlinx.android.synthetic.main.item_book.itemBookCheckbox
import kotlinx.android.synthetic.main.item_book.item_book_article_count
import kotlinx.android.synthetic.main.item_book.item_book_date
import kotlinx.android.synthetic.main.item_book.item_book_description
@ -17,6 +18,9 @@ import org.kiwix.kiwixmobile.main.KiwixWebView
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.zim_manager.KiloByte
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.ArticleCount
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode.MULTI
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.SelectionMode.NORMAL
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.LanguageItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.base.BaseViewHolder
@ -28,10 +32,17 @@ sealed class BookOnDiskViewHolder<T : BooksOnDiskListItem>(containerView: View)
containerView: View,
private val sharedPreferenceUtil: SharedPreferenceUtil,
private val clickAction: (BookOnDisk) -> Unit,
private val longClickAction: ((BookOnDisk) -> Unit)?
private val longClickAction: ((BookOnDisk) -> Unit)?,
private val multiSelectAction: ((BookOnDisk) -> Unit)?
) : BookOnDiskViewHolder<BookOnDisk>(containerView) {
override fun bind(item: BookOnDisk) {
}
fun bind(
item: BookOnDisk,
selectionMode: SelectionMode
) {
val book = item.book
item_book_title.text = book.getTitle()
item_book_date.text = book.getDate()
@ -59,24 +70,30 @@ sealed class BookOnDiskViewHolder<T : BooksOnDiskListItem>(containerView: View)
item_book_label_video.visibility = View.GONE
}
containerView.setOnClickListener {
clickAction.invoke(item)
}
containerView.setOnLongClickListener {
longClickAction?.invoke(item)
return@setOnLongClickListener true
itemBookCheckbox.isChecked = item.isSelected
when (selectionMode) {
MULTI -> {
itemBookCheckbox.visibility = View.VISIBLE
containerView.setOnClickListener { multiSelectAction?.invoke(item) }
containerView.setOnLongClickListener(null)
}
NORMAL -> {
itemBookCheckbox.visibility = View.GONE
containerView.setOnClickListener { clickAction.invoke(item) }
containerView.setOnLongClickListener {
longClickAction?.invoke(item)
return@setOnLongClickListener true
}
}
}
}
}
class LanguageItemViewHolder(containerView: View) :
BookOnDiskViewHolder<LanguageItem>(containerView) {
override fun bind(item: LanguageItem) {
header_language.text = item.text
}
}
}
class LanguageItemViewHolder(containerView: View) :
BookOnDiskViewHolder<LanguageItem>(containerView) {
override fun bind(item: LanguageItem) {
header_language.text = item.text
}
}

View File

@ -0,0 +1,42 @@
package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
import android.app.Activity
import org.kiwix.kiwixmobile.R.string
import org.kiwix.kiwixmobile.database.newdb.dao.NewBookDao
import org.kiwix.kiwixmobile.extensions.toast
import org.kiwix.kiwixmobile.utils.DialogShower
import org.kiwix.kiwixmobile.utils.KiwixDialog.DeleteZim
import org.kiwix.kiwixmobile.utils.files.FileUtils
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
import javax.inject.Inject
class DeleteFiles(val booksOnDiskListItem: List<BookOnDisk>) :
SideEffect<Unit> {
@Inject lateinit var dialogShower: DialogShower
@Inject lateinit var newBookDao: NewBookDao
override fun invokeWith(activity: Activity) {
activityComponent(activity).inject(this)
booksOnDiskListItem.forEach {
dialogShower.show(DeleteZim, {
if (deleteSpecificZimFile(it)) {
activity.toast(string.delete_specific_zim_toast)
} else {
activity.toast(string.delete_zim_failed)
}
})
}
}
private fun deleteSpecificZimFile(book: BookOnDisk): Boolean {
val file = book.file
FileUtils.deleteZimFile(file.path)
if (file.exists()) {
return false
}
newBookDao.delete(book.databaseId!!)
return true
}
}

View File

@ -0,0 +1,8 @@
package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
import android.app.Activity
object None : SideEffect<Unit> {
override fun invokeWith(activity: Activity) {
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.zim_manager.fileselect_view.effects
import android.app.Activity
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.data.ZimContentProvider
import org.kiwix.kiwixmobile.extensions.toast
import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
class OpenFile(val bookOnDisk: BookOnDisk): SideEffect<Unit> {
override fun invokeWith(activity: Activity) {
val file = bookOnDisk.file
ZimContentProvider.canIterate = false
if (!file.canRead()) {
activity.toast(R.string.error_filenotfound)
} else {
(activity as ZimManageActivity).finishResult(file.path)
}
}
}

View File

@ -0,0 +1,42 @@
package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.core.content.FileProvider
import org.kiwix.kiwixmobile.BuildConfig
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
class ShareFiles(val selectedBooks: List<BookOnDisk>) : SideEffect<Unit> {
override fun invokeWith(activity: Activity) {
val selectedFileShareIntent = Intent()
selectedFileShareIntent.action = Intent.ACTION_SEND_MULTIPLE
selectedFileShareIntent.type = "application/octet-stream"
val selectedFileContentURIs = selectedBooks.mapNotNull {
if (Build.VERSION.SDK_INT >= 24) {
FileProvider.getUriForFile(
activity,
BuildConfig.APPLICATION_ID + ".fileprovider",
it.file
)
} else {
Uri.fromFile(it.file)
}
}
selectedFileShareIntent.putParcelableArrayListExtra(
Intent.EXTRA_STREAM,
ArrayList(selectedFileContentURIs)
)
selectedFileShareIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
val shareChooserIntent = Intent.createChooser(
selectedFileShareIntent,
activity.getString(R.string.selected_file_cab_app_chooser_title)
)
if (shareChooserIntent.resolveActivity(activity.getPackageManager()) != null) {
activity.startActivity(shareChooserIntent) // Open the app chooser dialog
}
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.zim_manager.fileselect_view.effects
import android.app.Activity
import org.kiwix.kiwixmobile.KiwixApplication
interface SideEffect<T:Any?> {
fun invokeWith(activity: Activity):T
fun activityComponent(activity: Activity) =
KiwixApplication.getApplicationComponent().activityComponent().activity(activity).build()
}

View File

@ -0,0 +1,27 @@
package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
import android.app.Activity
import android.view.ActionMode
import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.extensions.startActionMode
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestDeleteMultiSelection
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel.FileSelectActions.RequestShareMultiSelection
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem
data class StartMultiSelection(
val bookOnDisk: BooksOnDiskListItem.BookOnDisk,
val fileSelectActions: PublishProcessor<FileSelectActions>
) : SideEffect<ActionMode?> {
override fun invokeWith(activity: Activity) =
activity.startActionMode(
R.menu.menu_zim_files_contextual,
mapOf(
R.id.zim_file_delete_item to { fileSelectActions.offer(RequestDeleteMultiSelection) },
R.id.zim_file_share_item to { fileSelectActions.offer(RequestShareMultiSelection) }
),
{ fileSelectActions.offer(FileSelectActions.MultiModeFinished) }
)
}

View File

@ -140,7 +140,7 @@ class LibraryFragment : BaseFragment() {
}
private fun onLibraryItemsChange(it: List<LibraryListItem>?) {
libraryAdapter.itemList = it!!
libraryAdapter.items = it!!
if (it.isEmpty()) {
libraryErrorText.setText(
if (isNotConnected) R.string.no_network_connection

View File

@ -30,7 +30,7 @@ abstract class BaseDelegateAdapter<ITEM>(
setHasStableIds(true)
}
var itemList: List<ITEM> = mutableListOf()
var items: List<ITEM> = mutableListOf()
set(value) {
field = value
notifyDataSetChanged()
@ -41,19 +41,19 @@ abstract class BaseDelegateAdapter<ITEM>(
viewType: Int
) = delegateManager.createViewHolder(parent, viewType)
override fun getItemCount() = itemList.size
override fun getItemCount() = items.size
override fun onBindViewHolder(
holder: ViewHolder,
position: Int
) {
delegateManager.onBindViewHolder(itemList[position], holder)
delegateManager.onBindViewHolder(items[position], holder)
}
override fun getItemViewType(position: Int) =
delegateManager.getViewTypeFor(itemList[position])
delegateManager.getViewTypeFor(items[position])
override fun getItemId(position: Int): Long {
return getIdFor(itemList[position])
return getIdFor(items[position])
}
abstract fun getIdFor(item:ITEM):Long

View File

@ -4,21 +4,35 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:background="?android:attr/selectableItemBackground"
android:paddingEnd="0dp"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:paddingStart="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:ignore="Overdraw"
>
<CheckBox
android:id="@+id/itemBookCheckbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:paddingRight="10dp"
android:paddingEnd="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlSymmetry"
/>
<ImageView
android:id="@+id/item_book_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:contentDescription="@string/favicon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintStart_toEndOf="@id/itemBookCheckbox"
app:layout_constraintTop_toTopOf="parent"
tools:src="@mipmap/kiwix_icon_round"
/>

View File

@ -1 +0,0 @@
values-id

View File

@ -1 +0,0 @@
values-he

View File

@ -1 +0,0 @@
values-yi

View File

@ -27,11 +27,31 @@ import java.io.File
fun bookOnDisk(
book: Book = book(),
databaseId: Long = 0L,
databaseId: Long? = 0L,
file: File = File("")
) = BookOnDisk(databaseId, book, file)
fun book(id: String = "0") = Book().apply { this.id = id }
fun book(
id: String = "0",
title: String = "",
size: String = "",
favicon: String = "",
creator: String = "",
publisher: String = "",
date: String = "",
description: String = "",
language: String = ""
) = Book().apply {
this.id = id
this.title = title
this.size = size
this.favicon = favicon
this.creator = creator
this.publisher = publisher
this.date = date
this.description = description
this.language = language
}
fun downloadStatus(
downloadId: Long = 0L,

View File

@ -0,0 +1,34 @@
/*
* 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
import io.reactivex.Scheduler
import io.reactivex.android.plugins.RxAndroidPlugins
import io.reactivex.plugins.RxJavaPlugins
fun setScheduler(replacementScheduler: Scheduler) {
RxJavaPlugins.setIoSchedulerHandler { scheduler -> replacementScheduler }
RxJavaPlugins.setComputationSchedulerHandler { scheduler -> replacementScheduler }
RxJavaPlugins.setNewThreadSchedulerHandler { scheduler -> replacementScheduler }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> replacementScheduler }
}
fun resetSchedulers() {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
}

View File

@ -0,0 +1,139 @@
/*
* 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.utils.files
import android.content.ContentResolver
import android.content.Context
import android.database.Cursor
import android.os.Environment
import android.provider.MediaStore.MediaColumns
import eu.mhutti1.utils.storage.StorageDevice
import eu.mhutti1.utils.storage.StorageDeviceUtils
import io.mockk.clearMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import java.io.File
class FileSearchTest {
private val context: Context = mockk()
private lateinit var fileSearch: FileSearch
private val externalStorageDirectory: File = mockk()
private val contentResolver: ContentResolver = mockk()
private val storageDevice: StorageDevice = mockk()
private val unitTestTempDirectoryPath = "unittest${File.separator}"
@BeforeEach
fun init() {
clearMocks(context, externalStorageDirectory, contentResolver, storageDevice)
deleteTempDirectory()
mockkStatic(StorageDeviceUtils::class)
mockkStatic(Environment::class)
every { Environment.getExternalStorageDirectory() } returns externalStorageDirectory
every { externalStorageDirectory.absolutePath } returns "/externalStorageDirectory"
every { context.contentResolver } returns contentResolver
every { StorageDeviceUtils.getStorageDevices(context, false) } returns arrayListOf(
storageDevice
)
every { storageDevice.name } returns "/deviceDir"
fileSearch = FileSearch(context)
}
@AfterAll
fun teardown() {
deleteTempDirectory()
}
@Nested
inner class FileSystem {
@Test
fun `scan of directory that doesn't exist returns nothing`() {
every { contentResolver.query(any(), any(), any(), any(), any()) } returns null
fileSearch.scan("doesNotExist")
.test()
.assertValue(listOf())
}
@Test
fun `scan of directory that has files returns files`() {
val zimFile = File.createTempFile("${unitTestTempDirectoryPath}fileToFind", ".zim")
val zimaaFile = File.createTempFile("${unitTestTempDirectoryPath}fileToFind2", ".zimaa")
File.createTempFile("${unitTestTempDirectoryPath}willNotFind", ".txt")
every { contentResolver.query(any(), any(), any(), any(), any()) } returns null
val fileList = fileSearch.scan(zimFile.parent)
.test()
.values()[0]
assertThat(fileList).containsExactlyInAnyOrder(zimFile, zimaaFile)
}
}
@Nested
inner class MediaStore {
@Test
fun `scan media store, if files are readable they are returned`() {
val fileToFind = File.createTempFile("${unitTestTempDirectoryPath}fileToFind", ".zim")
expectFromMediaStore(fileToFind)
fileSearch.scan("")
.test()
.assertValue(listOf(fileToFind))
}
@Test
fun `scan media store, if files are not readable they are not returned`() {
val unreadableFile = File.createTempFile("${unitTestTempDirectoryPath}fileToFind", ".zim")
expectFromMediaStore(unreadableFile)
unreadableFile.delete()
fileSearch.scan("")
.test()
.assertValue(listOf())
}
private fun expectFromMediaStore(fileToFind: File) {
val cursor = mockk<Cursor>()
every {
contentResolver.query(
null,
arrayOf(MediaColumns.DATA),
MediaColumns.DATA + " like ? or " + MediaColumns.DATA + " like ? ",
arrayOf("%." + "zim", "%." + "zimaa"),
null
)
} returns cursor
every { cursor.moveToNext() } returnsMany listOf(true, false)
every { cursor.columnNames } returns arrayOf(MediaColumns.DATA)
every { cursor.getColumnIndex(MediaColumns.DATA) } returns 0
every { cursor.getString(0) } returns fileToFind.absolutePath
}
}
private fun deleteTempDirectory() {
File.createTempFile("${unitTestTempDirectoryPath}temp", ".txt")
.parentFile.deleteRecursively()
}
}

View File

@ -20,16 +20,12 @@ package org.kiwix.kiwixmobile.zim_manager
import android.app.Application
import com.jraska.livedata.test
import io.mockk.clearMocks
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import io.reactivex.Scheduler
import io.reactivex.Single
import io.reactivex.android.plugins.RxAndroidPlugins
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers
import io.reactivex.schedulers.TestScheduler
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeEach
@ -55,12 +51,15 @@ import org.kiwix.kiwixmobile.downloader.model.DownloadStatus
import org.kiwix.kiwixmobile.downloader.model.UriToFileConverter
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.resetSchedulers
import org.kiwix.kiwixmobile.setScheduler
import org.kiwix.kiwixmobile.utils.BookUtils
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CanWrite4GbFile
import org.kiwix.kiwixmobile.zim_manager.Fat32Checker.FileSystemState.CannotWrite4GbFile
import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED
import org.kiwix.kiwixmobile.zim_manager.NetworkState.NOT_CONNECTED
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.FileSelectListState
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.StorageObserver
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.adapter.BooksOnDiskListItem.BookOnDisk
@ -104,26 +103,14 @@ class ZimManageViewModelTest {
setScheduler(testScheduler)
}
private fun setScheduler(replacementScheduler: Scheduler) {
RxJavaPlugins.setIoSchedulerHandler { scheduler -> replacementScheduler }
RxJavaPlugins.setComputationSchedulerHandler { scheduler -> replacementScheduler }
RxJavaPlugins.setNewThreadSchedulerHandler { scheduler -> replacementScheduler }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> Schedulers.trampoline() }
}
@AfterAll
fun teardown() {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
resetSchedulers()
}
@BeforeEach
fun init() {
clearMocks(
newDownloadDao, newBookDao, newLanguagesDao, downloader,
storageObserver, kiwixService, application, connectivityBroadcastReceiver, bookUtils,
fat32Checker, uriToFileConverter, defaultLanguageProvider, dataSource
)
clearAllMocks()
every { connectivityBroadcastReceiver.action } returns "test"
every { newDownloadDao.downloads() } returns downloads
every { newBookDao.books() } returns books
@ -235,8 +222,8 @@ class ZimManageViewModelTest {
val expectedList = listOf(bookOnDisk())
booksOnDiskListItems.onNext(expectedList)
testScheduler.triggerActions()
viewModel.bookItems.test()
.assertValue(expectedList)
viewModel.fileSelectListStates.test()
.assertValue(FileSelectListState(expectedList))
}
@Test

View File

@ -0,0 +1,149 @@
package org.kiwix.kiwixmobile.zim_manager.fileselect_view
/*
* 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/>.
*/
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify
import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.kiwix.kiwixmobile.book
import org.kiwix.kiwixmobile.bookOnDisk
import org.kiwix.kiwixmobile.data.ZimContentProvider
import org.kiwix.kiwixmobile.database.newdb.dao.NewDownloadDao
import org.kiwix.kiwixmobile.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.resetSchedulers
import org.kiwix.kiwixmobile.setScheduler
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.utils.files.FileSearch
import java.io.File
class StorageObserverTest {
private val sharedPreferenceUtil: SharedPreferenceUtil = mockk()
private val newDownloadDao: NewDownloadDao = mockk()
private val fileSearch: FileSearch = mockk()
private val downloadModel = mockk<DownloadModel>()
private val file = mockk<File>()
private val files: PublishProcessor<List<File>> = PublishProcessor.create()
private val downloads: PublishProcessor<List<DownloadModel>> = PublishProcessor.create()
private lateinit var storageObserver: StorageObserver
init {
setScheduler(Schedulers.trampoline())
}
@AfterAll
fun teardown() {
resetSchedulers()
}
@BeforeEach fun init() {
clearAllMocks()
every { sharedPreferenceUtil.prefStorage } returns "a"
every { fileSearch.scan("a") } returns files
every { newDownloadDao.downloads() } returns downloads
storageObserver = StorageObserver(sharedPreferenceUtil, newDownloadDao, fileSearch)
}
@Test
fun `books from disk are filtered by current downloads`() {
withFiltering()
booksOnFileSystem().assertValues(listOf())
}
@Test
fun `null books from ZimContentProvider are filtered out`() {
withNoFiltering()
booksOnFileSystem().assertValues(listOf())
}
@Test
fun `iterable ZimContentProvider with zim file produces a book`() {
val expectedBook = book(
"id", "title", "1", "favicon", "creator", "publisher", "date",
"description", "language"
)
mockkStatic(ZimContentProvider::class)
withNoFiltering()
every { ZimContentProvider.setZimFile("This won't match") } returns ""
expect(expectedBook)
booksOnFileSystem().assertValues(
listOf(
bookOnDisk(book = expectedBook, file = file, databaseId = null)
)
)
assertThat(ZimContentProvider.originalFileName).isEqualTo("")
}
@Test
fun `zim provider sets zim file to original file name if it exists`() {
withNoFiltering()
mockkStatic(ZimContentProvider::class)
every { ZimContentProvider.setZimFile(any()) } returns null
ZimContentProvider.zimFileName = "myZimFileName"
booksOnFileSystem().assertValues(listOf())
verify { ZimContentProvider.setZimFile("myZimFileName") }
}
@Test
fun `zim provider does not read book if it can not iterate`() {
withNoFiltering()
ZimContentProvider.canIterate = false
booksOnFileSystem().assertValues(listOf())
}
private fun booksOnFileSystem() = storageObserver.booksOnFileSystem
.test()
.also {
downloads.offer(listOf(downloadModel))
files.offer(listOf(file))
}
private fun expect(expectedBook: Book) {
every { ZimContentProvider.getZimFileTitle() } returns expectedBook.title
every { ZimContentProvider.getId() } returns expectedBook.id
every { ZimContentProvider.getFileSize() } returns expectedBook.size.toInt()
every { ZimContentProvider.getFavicon() } returns expectedBook.favicon
every { ZimContentProvider.getCreator() } returns expectedBook.creator
every { ZimContentProvider.getPublisher() } returns expectedBook.publisher
every { ZimContentProvider.getDate() } returns expectedBook.date
every { ZimContentProvider.getDescription() } returns expectedBook.description
every { ZimContentProvider.getLanguage() } returns expectedBook.language
}
private fun withFiltering() {
every { downloadModel.fileNameFromUrl } returns "test"
every { file.absolutePath } returns "This is a test"
}
private fun withNoFiltering() {
every { downloadModel.fileNameFromUrl } returns "test"
every { file.absolutePath } returns "This won't match"
}
}

View File

@ -35,7 +35,7 @@ ext {
set("powerMockVersion", "1.6.6")
set("powerMockJUnitVersion", "1.7.4")
set("baristaVersion", "2.7.1")
set("kotlinVersion", "1.3.31")
set("kotlinVersion", "1.3.40")
set("objectboxVersion", "2.3.4")
}