LibraryFragment converted to Kotlin and observing reactive streams

This commit is contained in:
Sean Mac Gillicuddy 2019-05-07 13:03:21 +01:00
parent 61697b502c
commit 41a753a1e6
48 changed files with 1164 additions and 1192 deletions

3
.gitignore vendored
View File

@ -53,4 +53,5 @@ glassify
.project
.classpath
.vscode
.vscode
captures/

View File

@ -51,7 +51,6 @@ import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
import static com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertDisplayed;
import static com.schibsted.spain.barista.interaction.BaristaClickInteractions.clickOn;
import static com.schibsted.spain.barista.interaction.BaristaDialogInteractions.clickDialogPositiveButton;
import static com.schibsted.spain.barista.interaction.BaristaSwipeRefreshInteractions.refresh;
import static junit.framework.Assert.fail;
import static org.hamcrest.Matchers.allOf;
@ -115,13 +114,13 @@ public class DownloadTest {
BaristaSleepInteractions.sleep(TEST_PAUSE_MS);
try {
onData(withContent("ray_charles")).inAdapterView(withId(R.id.library_list));
onData(withContent("ray_charles")).inAdapterView(withId(R.id.libraryList));
} catch (Exception e) {
fail("Couldn't find downloaded file 'ray_charles'\n\nOriginal Exception:\n" +
e.getLocalizedMessage() + "\n\n" );
}
deleteZimIfExists("ray_charles", R.id.library_list);
deleteZimIfExists("ray_charles", R.id.libraryList);
assertDisplayed(R.string.local_zims);
clickOn(R.string.local_zims);

View File

@ -137,7 +137,7 @@ public class NetworkTest {
"Permission dialog was not shown, we probably already have required permissions");
}
onData(withContent("wikipedia_ab_all_2017-03")).inAdapterView(withId(R.id.library_list)).perform(click());
onData(withContent("wikipedia_ab_all_2017-03")).inAdapterView(withId(R.id.libraryList)).perform(click());
try {
onView(withId(android.R.id.button1)).perform(click());

View File

@ -482,7 +482,7 @@ public class KiwixMobileActivity extends BaseActivity implements WebViewCallback
}
if (i.hasExtra(EXTRA_ZIM_FILE)) {
File file = new File(getFileName(i.getStringExtra(EXTRA_ZIM_FILE)));
LibraryFragment.mService.cancelNotification(i.getIntExtra(EXTRA_NOTIFICATION_ID, 0));
//LibraryFragment.mService.cancelNotification(i.getIntExtra(EXTRA_NOTIFICATION_ID, 0));
Uri uri = Uri.fromFile(file);
finish();

View File

@ -50,8 +50,14 @@ public class DownloadDao {
}
public void insert(final DownloadModel downloadModel) {
kiwixDatabase.persistWithOnConflict(databaseEntity(downloadModel),
TableStatement.ConflictAlgorithm.REPLACE);
if (doesNotAlreadyExist(downloadModel)) {
kiwixDatabase.persistWithOnConflict(databaseEntity(downloadModel),
TableStatement.ConflictAlgorithm.REPLACE);
}
}
private boolean doesNotAlreadyExist(DownloadModel downloadModel) {
return kiwixDatabase.count(DownloadDatabaseEntity.class, DownloadDatabaseEntity.BOOK_ID.eq(downloadModel.getBookId())) == 0;
}
public void delete(@NotNull Long... downloadIds) {

View File

@ -19,50 +19,62 @@
package org.kiwix.kiwixmobile.database;
import com.yahoo.squidb.data.SimpleDataChangedNotifier;
import com.yahoo.squidb.data.SquidCursor;
import com.yahoo.squidb.sql.Query;
import javax.inject.Inject;
import org.kiwix.kiwixmobile.database.entity.NetworkLanguageDatabaseEntity;
import org.kiwix.kiwixmobile.library.LibraryAdapter;
import org.kiwix.kiwixmobile.library.LibraryAdapter.Language;
import io.reactivex.Flowable;
import io.reactivex.processors.PublishProcessor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import org.kiwix.kiwixmobile.database.entity.NetworkLanguageDatabaseEntity;
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language;
public class NetworkLanguageDao {
private KiwixDatabase mDb;
private final PublishProcessor<List<Language>> languageProcessor =
PublishProcessor.create();
@Inject
public NetworkLanguageDao(KiwixDatabase kiwikDatabase) {
this.mDb = kiwikDatabase;
mDb.registerDataChangedNotifier(
new SimpleDataChangedNotifier(NetworkLanguageDatabaseEntity.TABLE) {
@Override
protected void onDataChanged() {
languageProcessor.onNext(getActiveLanguages());
}
});
}
public ArrayList<LibraryAdapter.Language> getFilteredLanguages() {
SquidCursor<NetworkLanguageDatabaseEntity> languageCursor = mDb.query(
public Flowable<List<Language>> activeLanguages() {
return languageProcessor.startWith(getActiveLanguages()).distinctUntilChanged();
}
public List<Language> getActiveLanguages() {
ArrayList<Language> result = new ArrayList<>();
try (SquidCursor<NetworkLanguageDatabaseEntity> languageCursor = mDb.query(
NetworkLanguageDatabaseEntity.class,
Query.select());
ArrayList<LibraryAdapter.Language> result = new ArrayList<>();
try {
Query.select().where(NetworkLanguageDatabaseEntity.ENABLED.eq(true)))) {
while (languageCursor.moveToNext()) {
String languageCode = languageCursor.get(NetworkLanguageDatabaseEntity.LANGUAGE_I_S_O_3);
boolean enabled = languageCursor.get(NetworkLanguageDatabaseEntity.ENABLED);
result.add(new LibraryAdapter.Language(languageCode, enabled));
result.add(new Language(languageCode, enabled));
}
} finally {
languageCursor.close();
}
return result;
}
public void saveFilteredLanguages(List<Language> languages){
public void saveFilteredLanguages(List<Language> languages) {
mDb.deleteAll(NetworkLanguageDatabaseEntity.class);
Collections.sort(languages, (language, t1) -> language.language.compareTo(t1.language));
for (LibraryAdapter.Language language : languages){
NetworkLanguageDatabaseEntity networkLanguageDatabaseEntity = new NetworkLanguageDatabaseEntity();
networkLanguageDatabaseEntity.setLanguageISO3(language.languageCode);
networkLanguageDatabaseEntity.setIsEnabled(language.active);
Collections.sort(languages,
(language, t1) -> language.getLanguage().compareTo(t1.getLanguage()));
for (Language language : languages) {
NetworkLanguageDatabaseEntity networkLanguageDatabaseEntity =
new NetworkLanguageDatabaseEntity();
networkLanguageDatabaseEntity.setLanguageISO3(language.getLanguageCode());
networkLanguageDatabaseEntity.setIsEnabled(language.getActive());
mDb.persist(networkLanguageDatabaseEntity);
}
}

View File

@ -27,7 +27,7 @@ import org.kiwix.kiwixmobile.di.modules.ApplicationModule;
import org.kiwix.kiwixmobile.di.modules.JNIModule;
import org.kiwix.kiwixmobile.di.modules.NetworkModule;
import org.kiwix.kiwixmobile.downloader.DownloadService;
import org.kiwix.kiwixmobile.library.LibraryAdapter;
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryAdapter;
import org.kiwix.kiwixmobile.settings.KiwixSettingsActivity;
import org.kiwix.kiwixmobile.views.AutoCompleteAdapter;
import org.kiwix.kiwixmobile.views.web.KiwixWebView;
@ -56,8 +56,6 @@ public interface ApplicationComponent {
void inject(ZimContentProvider zimContentProvider);
void inject(LibraryAdapter libraryAdapter);
void inject(KiwixWebView kiwixWebView);
void inject(KiwixSettingsActivity.PrefsFragment prefsFragment);

View File

@ -17,6 +17,7 @@
*/
package org.kiwix.kiwixmobile.di.modules;
import android.app.Application;
import android.app.DownloadManager;
import android.app.NotificationManager;
import android.content.Context;
@ -32,6 +33,10 @@ import org.kiwix.kiwixmobile.utils.BookUtils;
})
public class ApplicationModule {
@Provides @Singleton Application provideApplication(Context context) {
return (Application) context;
}
@Provides @Singleton NotificationManager provideNotificationManager(Context context) {
return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
}

View File

@ -201,7 +201,7 @@ public class DownloadService extends Service {
notificationManager.notify(notificationID, notification.get(notificationID).build());
downloadStatus.put(notificationID, PLAY);
LibraryFragment.downloadingBooks.remove(book);
//LibraryFragment.downloadingBooks.remove(book);
String url = intent.getExtras().getString(DownloadIntent.DOWNLOAD_URL_PARAMETER);
downloadBook(url, notificationID, book);
}

View File

@ -0,0 +1,12 @@
package org.kiwix.kiwixmobile.extensions
import android.net.ConnectivityManager
import org.kiwix.kiwixmobile.zim_manager.NetworkState
import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED
import org.kiwix.kiwixmobile.zim_manager.NetworkState.NOT_CONNECTED
val ConnectivityManager.networkState: NetworkState
get() = if (activeNetworkInfo?.isConnected == true)
CONNECTED
else
NOT_CONNECTED

View File

@ -1,7 +1,9 @@
package org.kiwix.kiwixmobile.extensions
import android.content.Context
import android.content.IntentFilter
import android.widget.Toast
import org.kiwix.kiwixmobile.zim_manager.BaseBroadcastReceiver
fun Context?.toast(
stringId: Int,
@ -11,4 +13,17 @@ fun Context?.toast(
Toast.makeText(this, stringId, length)
.show()
}
}
}
fun Context?.toast(
text: String,
length: Int = Toast.LENGTH_LONG
) {
this?.let {
Toast.makeText(this, text, length)
.show()
}
}
fun Context.registerReceiver(baseBroadcastReceiver: BaseBroadcastReceiver) =
registerReceiver(baseBroadcastReceiver, IntentFilter(baseBroadcastReceiver.action))

View File

@ -0,0 +1,12 @@
package org.kiwix.kiwixmobile.extensions
import android.view.View
import android.widget.TextView
fun TextView.setTextAndVisibility(nullableText: String?) =
if (nullableText != null && nullableText.isNotEmpty()) {
text = nullableText
visibility = View.VISIBLE
} else {
visibility = View.GONE
}

View File

@ -0,0 +1,21 @@
package org.kiwix.kiwixmobile.extensions
import android.graphics.Color
import android.support.design.widget.Snackbar
import android.view.View
fun View.snack(
stringId: Int,
actionStringId: Int,
actionClick: () -> Unit,
actionTextColor: Int = Color.WHITE
) {
Snackbar.make(
this, stringId, Snackbar.LENGTH_LONG
)
.setAction(actionStringId) {
actionClick.invoke()
}
.setActionTextColor(actionTextColor)
.show()
}

View File

@ -1,473 +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.library;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Base64;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.common.collect.ImmutableList;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.kiwix.kiwixmobile.KiwixApplication;
import org.kiwix.kiwixmobile.R;
import org.kiwix.kiwixmobile.database.BookDao;
import org.kiwix.kiwixmobile.database.NetworkLanguageDao;
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book;
import org.kiwix.kiwixmobile.utils.BookUtils;
import org.kiwix.kiwixmobile.zim_manager.library_view.LibraryFragment;
import static org.kiwix.kiwixmobile.utils.NetworkUtils.parseURL;
public class LibraryAdapter extends BaseAdapter {
private static final int LIST_ITEM_TYPE_BOOK = 0;
private static final int LIST_ITEM_TYPE_DIVIDER = 1;
private ImmutableList<Book> allBooks;
private List<ListItem> listItems = new ArrayList<>();
private final Context context;
public Map<String, Integer> languageCounts = new HashMap<>();
public List<Language> languages = new ArrayList<>();
private final LayoutInflater layoutInflater;
private final BookFilter bookFilter = new BookFilter();
private Disposable saveNetworkLanguageDisposable;
@Inject BookUtils bookUtils;
@Inject
NetworkLanguageDao networkLanguageDao;
@Inject
BookDao bookDao;
//TODO: restore functionality of commented out code
public LibraryAdapter(Context context) {
super();
KiwixApplication.getApplicationComponent().inject(this);
this.context = context;
layoutInflater = LayoutInflater.from(context);
}
public void setAllBooks(List<Book> books) {
allBooks = ImmutableList.copyOf(books);
updateLanguageCounts();
updateLanguages();
}
public boolean isDivider(int position) {
return listItems.get(position).type == LIST_ITEM_TYPE_DIVIDER;
}
@Override
public int getCount() {
return listItems.size();
}
@Override
public Object getItem(int i) {
return listItems.get(i).data;
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (position >= listItems.size()) {
return convertView;
}
ListItem item = listItems.get(position);
if (item.type == LIST_ITEM_TYPE_BOOK) {
if (convertView != null && convertView.findViewById(R.id.title) != null) {
holder = (ViewHolder) convertView.getTag();
} else {
convertView = layoutInflater.inflate(R.layout.library_item, null);
holder = new ViewHolder();
holder.title = convertView.findViewById(R.id.title);
holder.description = convertView.findViewById(R.id.description);
holder.language = convertView.findViewById(R.id.language);
holder.creator = convertView.findViewById(R.id.creator);
holder.publisher = convertView.findViewById(R.id.publisher);
holder.date = convertView.findViewById(R.id.date);
holder.size = convertView.findViewById(R.id.size);
holder.fileName = convertView.findViewById(R.id.fileName);
holder.favicon = convertView.findViewById(R.id.favicon);
convertView.setTag(holder);
}
Book book = (Book) listItems.get(position).data;
holder.title.setText(book.getTitle());
holder.description.setText(book.getDescription());
holder.language.setText(bookUtils.getLanguage(book.getLanguage()));
holder.creator.setText(book.getCreator());
holder.publisher.setText(book.getPublisher());
holder.date.setText(book.getDate());
holder.size.setText(createGbString(book.getSize()));
holder.fileName.setText(parseURL(context, book.getUrl()));
holder.favicon.setImageBitmap(createBitmapFromEncodedString(book.getFavicon(), context));
// Check if no value is empty. Set the view to View.GONE, if it is. To View.VISIBLE, if not.
if (book.getTitle() == null || book.getTitle().isEmpty()) {
holder.title.setVisibility(View.GONE);
} else {
holder.title.setVisibility(View.VISIBLE);
}
if (book.getDescription() == null || book.getDescription().isEmpty()) {
holder.description.setVisibility(View.GONE);
} else {
holder.description.setVisibility(View.VISIBLE);
}
if (book.getCreator() == null || book.getCreator().isEmpty()) {
holder.creator.setVisibility(View.GONE);
} else {
holder.creator.setVisibility(View.VISIBLE);
}
if (book.getPublisher() == null || book.getPublisher().isEmpty()) {
holder.publisher.setVisibility(View.GONE);
} else {
holder.publisher.setVisibility(View.VISIBLE);
}
if (book.getDate() == null || book.getDate().isEmpty()) {
holder.date.setVisibility(View.GONE);
} else {
holder.date.setVisibility(View.VISIBLE);
}
if (book.getSize() == null || book.getSize().isEmpty()) {
holder.size.setVisibility(View.GONE);
} else {
holder.size.setVisibility(View.VISIBLE);
}
return convertView;
} else {
if (convertView != null && convertView.findViewById(R.id.divider_text) != null) {
holder = (ViewHolder) convertView.getTag();
} else {
convertView = layoutInflater.inflate(R.layout.library_divider, null);
holder = new ViewHolder();
holder.title = convertView.findViewById(R.id.divider_text);
convertView.setTag(holder);
}
String dividerText = (String) listItems.get(position).data;
holder.title.setText(dividerText);
return convertView;
}
}
private boolean languageActive(Book book) {
return Observable.fromIterable(languages)
.filter(language -> language.languageCode.equals(book.getLanguage()))
.firstElement()
.map(language -> language.active)
.blockingGet(false);
}
private Observable<Book> getMatches(Book b, String s) {
StringBuilder text = new StringBuilder();
text.append(b.getTitle()).append("|").append(b.getDescription()).append("|")
.append(parseURL(context, b.getUrl())).append("|");
if (bookUtils.localeMap.containsKey(b.getLanguage())) {
text.append(bookUtils.localeMap.get(b.getLanguage()).getDisplayLanguage()).append("|");
}
String[] words = s.toLowerCase().split("\\s+");
b.searchMatches = Observable.fromArray(words)
.filter(text.toString().toLowerCase()::contains)
.count()
.blockingGet()
.intValue();
if (b.searchMatches > 0) {
return Observable.just(b);
} else {
return Observable.empty();
}
}
private class BookFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence s) {
List<Book> books = bookDao.getBooks();
listItems.clear();
if (s.length() == 0) {
List<Book> selectedLanguages = Observable.fromIterable(allBooks)
.filter(LibraryAdapter.this::languageActive)
.filter(book -> !books.contains(book))
// .filter(book -> !DownloadFragment.mDownloads.values().contains(book))
.filter(book -> !LibraryFragment.downloadingBooks.contains(book))
.filter(book -> !book.url.contains("/stack_exchange/")) // Temp filter see #694
.toList()
.blockingGet();
List<Book> unselectedLanguages = Observable.fromIterable(allBooks)
.filter(book -> !languageActive(book))
.filter(book -> !books.contains(book))
// .filter(book -> !DownloadFragment.mDownloads.values().contains(book))
.filter(book -> !LibraryFragment.downloadingBooks.contains(book))
.filter(book -> !book.url.contains("/stack_exchange/")) // Temp filter see #694
.toList()
.blockingGet();
listItems.add(new ListItem(context.getResources().getString(R.string.your_languages), LIST_ITEM_TYPE_DIVIDER));
addBooks(selectedLanguages);
listItems.add(new ListItem(context.getResources().getString(R.string.other_languages), LIST_ITEM_TYPE_DIVIDER));
addBooks(unselectedLanguages);
} else {
List<Book> selectedLanguages = Observable.fromIterable(allBooks)
.filter(LibraryAdapter.this::languageActive)
.filter(book -> !books.contains(book))
// .filter(book -> !DownloadFragment.mDownloads.values().contains(book))
.filter(book -> !LibraryFragment.downloadingBooks.contains(book))
.filter(book -> !book.url.contains("/stack_exchange/")) // Temp filter see #694
.flatMap(book -> getMatches(book, s.toString()))
.toList()
.blockingGet();
Collections.sort(selectedLanguages, new BookMatchComparator());
List<Book> unselectedLanguages = Observable.fromIterable(allBooks)
.filter(book -> !languageActive(book))
.filter(book -> !books.contains(book))
// .filter(book -> !DownloadFragment.mDownloads.values().contains(book))
.filter(book -> !LibraryFragment.downloadingBooks.contains(book))
.filter(book -> !book.url.contains("/stack_exchange/")) // Temp filter see #694
.flatMap(book -> getMatches(book, s.toString()))
.toList()
.blockingGet();
Collections.sort(unselectedLanguages, new BookMatchComparator());
listItems.add(new ListItem("In your language:", LIST_ITEM_TYPE_DIVIDER));
addBooks(selectedLanguages);
listItems.add(new ListItem("In other languages:", LIST_ITEM_TYPE_DIVIDER));
addBooks(unselectedLanguages);
}
FilterResults results = new FilterResults();
results.values = listItems;
results.count = listItems.size();
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
List<ListItem> filtered = (List<ListItem>) results.values;
if (filtered != null) {
if (filtered.isEmpty()) {
addBooks(allBooks);
}
}
notifyDataSetChanged();
}
}
public Filter getFilter() {
return bookFilter;
}
public void updateNetworkLanguages() {
saveNetworkLanguages();
}
private void updateLanguageCounts() {
languageCounts.clear();
for (Book book : allBooks) {
Integer cnt = languageCounts.get(book.getLanguage());
if (cnt == null) {
languageCounts.put(book.getLanguage(), 1);
} else {
languageCounts.put(book.getLanguage(), cnt + 1);
}
}
}
private void updateLanguages() {
// Load previously stored languages and extract which ones were enabled. The new book list might
// have new languages, or be missing some old ones so we want to refresh it, but retain user's
// selections.
Set<String> enabled_languages = new HashSet<>();
for (Language language : networkLanguageDao.getFilteredLanguages()) {
if (language.active) {
enabled_languages.add(language.languageCode);
}
}
// Populate languages with all available locales, which appear in the current list of all books.
this.languages = new ArrayList<>();
for (String iso_language : Locale.getISOLanguages()) {
Locale locale = new Locale(iso_language);
if (languageCounts.get(locale.getISO3Language()) != null) {
// Enable this language either if it was enabled previously, or if it is the device language.
if (enabled_languages.contains(locale.getISO3Language()) ||
context.getResources().getConfiguration().locale.getISO3Language().equals(locale.getISO3Language())) {
this.languages.add(new Language(locale, true));
} else {
this.languages.add(new Language(locale, false));
}
}
}
saveNetworkLanguages();
}
private void addBooks(List<Book> books) {
for (Book book : books) {
listItems.add(new ListItem(book, LIST_ITEM_TYPE_BOOK));
}
}
// Create a string that represents the size of the zim file in a human readable way
public static String createGbString(String megaByte) {
int size = 0;
try {
size = Integer.parseInt(megaByte);
} catch (NumberFormatException e) {
e.printStackTrace();
}
if (size <= 0) {
return "";
}
final String[] units = new String[]{"KB", "MB", "GB", "TB"};
int conversion = (int) (Math.log10(size) / Math.log10(1024));
return new DecimalFormat("#,##0.#")
.format(size / Math.pow(1024, conversion))
+ " "
+ units[conversion];
}
// Decode and create a Bitmap from the 64-Bit encoded favicon string
public static Bitmap createBitmapFromEncodedString(String encodedString, Context context) {
try {
byte[] decodedString = Base64.decode(encodedString, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
} catch (Exception e) {
e.printStackTrace();
}
return BitmapFactory.decodeResource(context.getResources(), R.mipmap.kiwix_icon);
}
private static class ViewHolder {
TextView title;
TextView description;
TextView language;
TextView creator;
TextView publisher;
TextView date;
TextView size;
TextView fileName;
ImageView favicon;
}
private class ListItem {
public Object data;
public int type;
public ListItem(Object data, int type) {
this.data = data;
this.type = type;
}
}
private class BookMatchComparator implements Comparator<Book> {
public int compare(Book book1, Book book2) {
return book2.searchMatches - book1.searchMatches;
}
}
public static class Language {
public String language;
public String languageLocalized;
public String languageCode;
public String languageCodeISO2;
public Boolean active;
Language(Locale locale, Boolean active) {
this.language = locale.getDisplayLanguage();
this.languageLocalized = locale.getDisplayLanguage(locale);
this.languageCode = locale.getISO3Language();
this.languageCodeISO2 = locale.getLanguage();
this.active = active;
}
public Language(String languageCode, Boolean active) {
this(new Locale(languageCode), active);
}
@Override
public boolean equals(Object obj) {
return ((Language) obj).language.equals(language) &&
((Language) obj).active.equals(active);
}
}
private void saveNetworkLanguages() {
if (saveNetworkLanguageDisposable != null && !saveNetworkLanguageDisposable.isDisposed()) {
saveNetworkLanguageDisposable.dispose();
}
saveNetworkLanguageDisposable = Completable.fromAction(() -> networkLanguageDao.saveFilteredLanguages(languages))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
}
}

View File

@ -29,7 +29,7 @@ import org.simpleframework.xml.Root;
public class LibraryNetworkEntity {
@ElementList(name = "book", inline = true, required = false)
private LinkedList<Book> book;
public LinkedList<Book> book;
@Attribute(name = "version", required = false)
private String version;

View File

@ -17,12 +17,12 @@
*/
package org.kiwix.kiwixmobile.network;
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity;
import org.kiwix.kiwixmobile.library.entity.MetaLinkNetworkEntity;
import io.reactivex.Flowable;
import io.reactivex.Observable;
import io.reactivex.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity;
import org.kiwix.kiwixmobile.library.entity.MetaLinkNetworkEntity;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.simplexml.SimpleXmlConverterFactory;
@ -30,7 +30,7 @@ import retrofit2.http.GET;
import retrofit2.http.Url;
public interface KiwixService {
@GET("/library/library_zim.xml") Observable<LibraryNetworkEntity> getLibrary();
@GET("/library/library_zim.xml") Flowable<LibraryNetworkEntity> getLibrary();
@GET Observable<MetaLinkNetworkEntity> getMetaLinks(@Url String url);

View File

@ -16,7 +16,7 @@ sealed class KiwixDialog(
open class YesNoDialog(
title: Int,
message: Int
) : KiwixDialog(title, message, R.string.yes, R.string.no){
) : KiwixDialog(title, message, R.string.yes, R.string.no) {
object NoWifi : YesNoDialog(
R.string.wifi_only_title, R.string.wifi_only_msg
)
@ -25,9 +25,8 @@ sealed class KiwixDialog(
R.string.confirm_stop_download_title, R.string.confirm_stop_download_msg
)
object WifiOnly : YesNoDialog(
R.string.wifi_only_title, R.string.wifi_only_msg
)
}
}

View File

@ -19,7 +19,6 @@
package org.kiwix.kiwixmobile.utils;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Typeface;
@ -33,7 +32,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import org.kiwix.kiwixmobile.library.LibraryAdapter;
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language;
import org.kiwix.kiwixmobile.utils.files.FileUtils;
import java.lang.reflect.Field;
@ -45,7 +44,6 @@ import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import static org.kiwix.kiwixmobile.utils.Constants.PREF_LANG;
import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX;
public class LanguageUtils {
@ -239,11 +237,11 @@ public class LanguageUtils {
return values;
}
public List<LibraryAdapter.Language> getLanguageList() {
List<LibraryAdapter.Language> values = new ArrayList<>();
public List<Language> getLanguageList() {
List<Language> values = new ArrayList<>();
for (LanguageContainer value : mLanguageList) {
values.add(new LibraryAdapter.Language(value.getLanguageCode(), false));
values.add(new Language(value.getLanguageCode(), false));
}
return values;

View File

@ -27,6 +27,7 @@ import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import java.util.ArrayList;
import org.kiwix.kiwixmobile.ZimContentProvider;
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity;
@ -109,11 +110,13 @@ public class FileSearch {
FilenameFilter[] filter = new FilenameFilter[zimFiles.length];
// Search all external directories that we can find.
String[] tempRoots = new String[StorageDeviceUtils.getStorageDevices(context, false).size() + 2];
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 : StorageDeviceUtils.getStorageDevices(context, false)) {
for (StorageDevice storageDevice : storageDevices) {
tempRoots[j++] = storageDevice.getName();
}

View File

@ -30,7 +30,7 @@ import android.widget.ListView;
import android.widget.TextView;
import org.kiwix.kiwixmobile.R;
import org.kiwix.kiwixmobile.library.LibraryAdapter;
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language;
import org.kiwix.kiwixmobile.utils.LanguageUtils;
import java.util.List;
@ -46,7 +46,7 @@ public class LanguageSelectDialog extends AlertDialog {
}
public static class Builder extends AlertDialog.Builder {
private List<LibraryAdapter.Language> languages;
private List<Language> languages;
private Map<String, Integer> languageCounts;
private boolean singleSelect = false;
private String selectedLanguage;
@ -60,7 +60,7 @@ public class LanguageSelectDialog extends AlertDialog {
super(context, themeResId);
}
public Builder setLanguages(List<LibraryAdapter.Language> languages) {
public Builder setLanguages(List<Language> languages) {
this.languages = languages;
return this;
}
@ -114,13 +114,13 @@ public class LanguageSelectDialog extends AlertDialog {
}
}
private static class LanguageArrayAdapter extends ArrayAdapter<LibraryAdapter.Language> {
private static class LanguageArrayAdapter extends ArrayAdapter<Language> {
private Map<String, Integer> languageCounts;
private Context context;
private boolean singleSelect;
private String selectedLanguage;
public LanguageArrayAdapter(Context context, int textViewResourceId, List<LibraryAdapter.Language> languages,
public LanguageArrayAdapter(Context context, int textViewResourceId, List<Language> languages,
Map<String, Integer> languageCounts, boolean singleSelect, String selectedLanguage) {
super(context, textViewResourceId, languages);
this.languageCounts = languageCounts;
@ -150,36 +150,36 @@ public class LanguageSelectDialog extends AlertDialog {
// Set event listeners first, since updating the default values can trigger them.
holder.row.setOnClickListener((view) -> holder.checkBox.toggle());
holder.checkBox
.setOnCheckedChangeListener((compoundButton, b) -> getItem(position).active = b);
.setOnCheckedChangeListener((compoundButton, b) -> getItem(position).setActive(b));
LibraryAdapter.Language language = getItem(position);
holder.language.setText(language.language);
Language language = getItem(position);
holder.language.setText(language.getLanguage());
holder.languageLocalized.setText(context.getString(R.string.language_localized,
language.languageLocalized));
language.getLanguageLocalized()));
holder.languageLocalized.setTypeface(Typeface.createFromAsset(context.getAssets(),
LanguageUtils.getTypeface(language.languageCode)));
LanguageUtils.getTypeface(language.getLanguageCode())));
if (languageCounts != null) {
holder.languageEntriesCount.setText(context.getString(R.string.language_count,
languageCounts.get(language.languageCode)));
languageCounts.get(language.getLanguageCode())));
} else {
holder.languageEntriesCount.setVisibility(View.GONE);
}
if (!singleSelect) {
holder.checkBox.setChecked(language.active);
holder.checkBox.setChecked(language.getActive());
} else {
holder.checkBox.setClickable(false);
holder.checkBox.setFocusable(false);
if (getSelectedLanguage().equalsIgnoreCase(language.languageCodeISO2)) {
if (getSelectedLanguage().equalsIgnoreCase(language.getLanguageCodeISO2())) {
holder.checkBox.setChecked(true);
} else {
holder.checkBox.setChecked(false);
}
convertView.setOnClickListener((v -> {
setSelectedLanguage(language.languageCodeISO2);
setSelectedLanguage(language.getLanguageCodeISO2());
notifyDataSetChanged();
}));
}

View File

@ -0,0 +1,25 @@
package org.kiwix.kiwixmobile.zim_manager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
abstract class BaseBroadcastReceiver : BroadcastReceiver() {
abstract val action:String
override fun onReceive(
context: Context,
intent: Intent?
) {
if (intent?.action == action) {
onIntentWithActionReceived(context, intent)
}
}
abstract fun onIntentWithActionReceived(
context: Context,
intent: Intent
)
}

View File

@ -0,0 +1,26 @@
package org.kiwix.kiwixmobile.zim_manager
import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import io.reactivex.processors.PublishProcessor
import org.kiwix.kiwixmobile.extensions.networkState
import javax.inject.Inject
class ConnectivityBroadcastReceiver @Inject constructor(private val connectivityManager: ConnectivityManager) :
BaseBroadcastReceiver() {
override val action: String = ConnectivityManager.CONNECTIVITY_ACTION
private val _networkStates =
PublishProcessor.create<NetworkState>()
val networkStates = _networkStates.startWith(connectivityManager.networkState)
override fun onIntentWithActionReceived(
context: Context,
intent: Intent
) {
_networkStates.onNext(connectivityManager.networkState)
}
}

View File

@ -0,0 +1,6 @@
package org.kiwix.kiwixmobile.zim_manager
enum class NetworkState {
CONNECTED,
NOT_CONNECTED
}

View File

@ -33,7 +33,6 @@ import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import java.io.File;
import javax.inject.Inject;
import org.kiwix.kiwixmobile.KiwixMobileActivity;
@ -217,9 +216,9 @@ public class ZimManageActivity extends BaseActivity implements ZimManageViewCall
public boolean onQueryTextChange(String s) {
searchQuery = s;
if (mSectionsPagerAdapter.libraryFragment.libraryAdapter != null) {
mSectionsPagerAdapter.libraryFragment.libraryAdapter.getFilter().filter(searchQuery);
}
//if (mSectionsPagerAdapter.libraryFragment.libraryAdapter != null) {
// mSectionsPagerAdapter.libraryFragment.libraryAdapter.getFilter().filter(searchQuery);
//}
mViewPager.setCurrentItem(1);
return true;
}
@ -235,11 +234,11 @@ public class ZimManageActivity extends BaseActivity implements ZimManageViewCall
switch (item.getItemId()) {
case R.id.select_language:
if (mViewPager.getCurrentItem() == 1) {
if(mSectionsPagerAdapter.libraryFragment.libraryAdapter.languages.size() == 0) {
Toast.makeText(this, R.string.wait_for_load, Toast.LENGTH_LONG).show();
} else {
showLanguageSelect();
}
//if(mSectionsPagerAdapter.libraryFragment.libraryAdapter.languages.size() == 0) {
// Toast.makeText(this, R.string.wait_for_load, Toast.LENGTH_LONG).show();
//} else {
// showLanguageSelect();
//}
}
default:
return super.onOptionsItemSelected(item);
@ -262,11 +261,11 @@ public class ZimManageActivity extends BaseActivity implements ZimManageViewCall
private void showLanguageSelect() {
new LanguageSelectDialog.Builder(this, dialogStyle())
.setLanguages(mSectionsPagerAdapter.libraryFragment.libraryAdapter.languages)
.setLanguageCounts(mSectionsPagerAdapter.libraryFragment.libraryAdapter.languageCounts)
//.setLanguages(mSectionsPagerAdapter.libraryFragment.libraryAdapter.languages)
//.setLanguageCounts(mSectionsPagerAdapter.libraryFragment.libraryAdapter.languageCounts)
.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
mSectionsPagerAdapter.libraryFragment.libraryAdapter.updateNetworkLanguages();
mSectionsPagerAdapter.libraryFragment.libraryAdapter.getFilter().filter(searchQuery);
//mSectionsPagerAdapter.libraryFragment.libraryAdapter.updateNetworkLanguages();
//mSectionsPagerAdapter.libraryFragment.libraryAdapter.getFilter().filter(searchQuery);
})
.show();
}

View File

@ -1,22 +1,35 @@
package org.kiwix.kiwixmobile.zim_manager
import android.app.Application
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.reactivex.Flowable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.functions.BiFunction
import io.reactivex.functions.Function4
import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers
import org.kiwix.kiwixmobile.R.string
import org.kiwix.kiwixmobile.database.BookDao
import org.kiwix.kiwixmobile.database.DownloadDao
import org.kiwix.kiwixmobile.database.NetworkLanguageDao
import org.kiwix.kiwixmobile.downloader.Downloader
import org.kiwix.kiwixmobile.downloader.model.DownloadItem
import org.kiwix.kiwixmobile.downloader.model.DownloadModel
import org.kiwix.kiwixmobile.downloader.model.DownloadState.Successful
import org.kiwix.kiwixmobile.downloader.model.DownloadStatus
import org.kiwix.kiwixmobile.extensions.registerReceiver
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.network.KiwixService
import org.kiwix.kiwixmobile.zim_manager.fileselect_view.StorageObserver
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.Language
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
import java.util.LinkedList
import java.util.Locale
import java.util.concurrent.TimeUnit.SECONDS
import javax.inject.Inject
@ -40,30 +53,160 @@ import javax.inject.Inject
class ZimManageViewModel @Inject constructor(
private val downloadDao: DownloadDao,
private val bookDao: BookDao,
private val languageDao: NetworkLanguageDao,
private val downloader: Downloader,
private val storageObserver: StorageObserver
private val storageObserver: StorageObserver,
private val kiwixService: KiwixService,
private val context: Application,
private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver
) : ViewModel() {
val libraryItems: MutableLiveData<List<LibraryListItem>> = MutableLiveData()
val downloadItems: MutableLiveData<List<DownloadItem>> = MutableLiveData()
val bookItems: MutableLiveData<List<Book>> = MutableLiveData()
val checkFileSystem = PublishProcessor.create<Unit>()
val deviceListIsRefreshing = MutableLiveData<Boolean>()
val libraryListIsRefreshing = MutableLiveData<Boolean>()
val networkStates = MutableLiveData<NetworkState>()
val requestFileSystemCheck = PublishProcessor.create<Unit>()
val requestDownloadLibrary = PublishProcessor.create<Unit>()
private val compositeDisposable = CompositeDisposable()
init {
val downloadStatuses = downloadStatuses()
val booksFromDao = books()
compositeDisposable.addAll(
compositeDisposable.addAll(*disposables())
requestDownloadLibrary.onNext(Unit)
context.registerReceiver(connectivityBroadcastReceiver)
}
override fun onCleared() {
compositeDisposable.clear()
context.unregisterReceiver(connectivityBroadcastReceiver)
super.onCleared()
}
private fun disposables(): Array<Disposable> {
val downloads: Flowable<MutableList<DownloadModel>> = downloadDao.downloads()
val downloadStatuses = downloadStatuses(downloads)
val booksFromDao: Flowable<List<Book>> = books()
val library = libraryFromNetwork()
return arrayOf(
updateDownloadItems(downloadStatuses),
removeCompletedDownloadsFromDb(downloadStatuses),
updateBookItems(booksFromDao),
checkFileSystemForBooksOnRequest(booksFromDao)
checkFileSystemForBooksOnRequest(booksFromDao),
updateLibraryItems(booksFromDao, downloads, library),
updateActiveLanguages(library),
updateNetworkStates()
)
}
private fun checkFileSystemForBooksOnRequest(booksFromDao: Flowable<List<Book>>): Disposable? {
return checkFileSystem
private fun updateNetworkStates() =
connectivityBroadcastReceiver.networkStates.subscribe(
networkStates::postValue, Throwable::printStackTrace
)
private fun libraryFromNetwork() =
Flowable.combineLatest(
requestDownloadLibrary,
connectivityBroadcastReceiver.networkStates.filter(
NetworkState.CONNECTED::equals
),
BiFunction<Unit, NetworkState, Unit> { _, _ -> Unit }
)
.subscribeOn(Schedulers.io())
.doOnNext { libraryListIsRefreshing.postValue(true) }
.switchMap { kiwixService.library }
.doOnError(Throwable::printStackTrace)
.onErrorResumeNext(Flowable.just(LibraryNetworkEntity().apply { book = LinkedList() }))
.doOnNext { libraryListIsRefreshing.postValue(false) }
private fun updateLibraryItems(
booksFromDao: Flowable<List<Book>>,
downloads: Flowable<MutableList<DownloadModel>>,
library: Flowable<LibraryNetworkEntity>?
) = Flowable.combineLatest(
booksFromDao,
downloads,
languageDao.activeLanguages().filter { it.isNotEmpty() },
library,
Function4(this::combineLibrarySources)
)
.subscribeOn(Schedulers.io())
.subscribe(
libraryItems::postValue,
Throwable::printStackTrace
)
private fun updateActiveLanguages(library: Flowable<LibraryNetworkEntity>) = library
.subscribeOn(Schedulers.io())
.map { it.books }
.withLatestFrom(
languageDao.activeLanguages(),
BiFunction(this::combineToLanguageList)
)
.subscribe(
languageDao::saveFilteredLanguages,
Throwable::printStackTrace
)
private fun combineToLanguageList(
booksFromNetwork: List<Book>,
activeLanguages: List<Language>
): List<Language> {
val languagesFromNetwork = booksFromNetwork.distinctBy { it.language }
.map { it.language }
return Locale.getISOLanguages()
.map { Locale(it) }
.filter { languagesFromNetwork.contains(it.isO3Language) }
.map { locale ->
Language(
locale.isO3Language,
languageWasPreviouslyActiveOrIsPrimaryLanguage(activeLanguages, locale)
)
}
.ifEmpty { listOf(Language(context.resources.configuration.locale.isO3Language, true)) }
}
private fun languageWasPreviouslyActiveOrIsPrimaryLanguage(
activeLanguages: List<Language>,
locale: Locale
) = activeLanguages.firstOrNull { it.languageCode == locale.isO3Language }?.let { true }
?: isPrimaryLocale(locale)
private fun isPrimaryLocale(locale: Locale) =
context.resources.configuration.locale.isO3Language == locale.isO3Language
private fun combineLibrarySources(
booksOnFileSystem: List<Book>,
activeDownloads: List<DownloadModel>,
activeLanguages: List<Language>,
libraryNetworkEntity: LibraryNetworkEntity
): List<LibraryListItem> {
val downloadedBooksIds = booksOnFileSystem.map { it.id }
val downloadingBookIds = activeDownloads.map { it.bookId }
val activeLanguageCodes = activeLanguages.map { it.languageCode }
val booksUnfilteredByLanguage = libraryNetworkEntity.books
.filterNot { downloadedBooksIds.contains(it.id) }
.filterNot { downloadingBookIds.contains(it.id) }
.filterNot { it.url.contains("/stack_exchange/") }// Temp filter see #694
return listOf(
DividerItem(Long.MAX_VALUE, context.getString(string.your_languages)),
*toBookItems(
booksUnfilteredByLanguage.filter { activeLanguageCodes.contains(it.language) }
),
DividerItem(Long.MIN_VALUE, context.getString(string.other_languages)),
*toBookItems(
booksUnfilteredByLanguage.filterNot { activeLanguageCodes.contains(it.language) }
)
)
}
private fun toBookItems(books: List<Book>) =
books.map { BookItem(it) }.toTypedArray()
private fun checkFileSystemForBooksOnRequest(booksFromDao: Flowable<List<Book>>) =
requestFileSystemCheck
.doOnNext { deviceListIsRefreshing.postValue(true) }
.switchMap {
updateBookDaoFromFilesystem(booksFromDao)
@ -73,20 +216,24 @@ class ZimManageViewModel @Inject constructor(
bookDao::saveBooks,
Throwable::printStackTrace
)
}
private fun books() = bookDao.books()
.subscribeOn(Schedulers.io())
.map { it.sortedBy { book -> book.title } }
private fun updateBookDaoFromFilesystem(booksFromDao: Flowable<List<Book>>) =
storageObserver.booksOnFileSystem.withLatestFrom(
booksFromDao,
BiFunction<Collection<Book>, List<Book>, List<Book>> { booksFileSystem, booksDao ->
booksFileSystem.minus(
booksDao
)
})
storageObserver.booksOnFileSystem
.withLatestFrom(
booksFromDao,
BiFunction(this::removeBooksAlreadyInDao)
)
private fun removeBooksAlreadyInDao(
booksFromFileSystem: Collection<Book>,
booksFromDao: List<Book>
) = booksFromFileSystem.minus(
booksFromDao
)
private fun updateBookItems(booksFromDao: Flowable<List<Book>>) =
booksFromDao
@ -113,17 +260,13 @@ class ZimManageViewModel @Inject constructor(
Throwable::printStackTrace
)
private fun downloadStatuses() = Flowable.combineLatest(
downloadDao.downloads(),
Flowable.interval(1, SECONDS),
BiFunction { downloadModels: List<DownloadModel>, _: Long -> downloadModels }
)
.subscribeOn(Schedulers.io())
.map(downloader::queryStatus)
.distinctUntilChanged()
override fun onCleared() {
compositeDisposable.clear()
super.onCleared()
}
private fun downloadStatuses(downloads: Flowable<MutableList<DownloadModel>>) =
Flowable.combineLatest(
downloads,
Flowable.interval(1, SECONDS),
BiFunction { downloadModels: List<DownloadModel>, _: Long -> downloadModels }
)
.subscribeOn(Schedulers.io())
.map(downloader::queryStatus)
.distinctUntilChanged()
}

View File

@ -8,11 +8,11 @@ import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.utils.BookUtils
// The Adapter for the ListView for when the ListView is populated with the rescanned files
public class RescanDataAdapter(
val bookUtils: BookUtils,
val onItemClick: (Book) -> Unit,
val onItemLongClick: (Book) -> Unit
) : RecyclerView.Adapter<RescanViewHolder>() {
class BooksAdapter(
private val bookUtils: BookUtils,
private val onItemClick: (Book) -> Unit,
private val onItemLongClick: (Book) -> Unit
) : RecyclerView.Adapter<BooksViewHolder>() {
init {
setHasStableIds(true)
@ -29,12 +29,12 @@ public class RescanDataAdapter(
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
) = RescanViewHolder(parent.inflate(layout.library_item, false), bookUtils)
) = BooksViewHolder(parent.inflate(layout.library_item, false), bookUtils)
override fun getItemCount() = itemList.size
override fun onBindViewHolder(
holder: RescanViewHolder,
holder: BooksViewHolder,
position: Int
) {
holder.bind(itemList[position], onItemClick, onItemLongClick)

View File

@ -2,9 +2,7 @@ package org.kiwix.kiwixmobile.zim_manager.fileselect_view
import android.support.v7.widget.RecyclerView.ViewHolder
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.TextView
import butterknife.internal.DebouncingOnClickListener
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.library_item.creator
import kotlinx.android.synthetic.main.library_item.date
@ -18,13 +16,15 @@ import kotlinx.android.synthetic.main.library_item.title
import org.kiwix.kiwixmobile.KiwixApplication
import org.kiwix.kiwixmobile.downloader.model.Base64String
import org.kiwix.kiwixmobile.extensions.setBitmap
import org.kiwix.kiwixmobile.extensions.setTextAndVisibility
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.utils.BookUtils
import org.kiwix.kiwixmobile.utils.NetworkUtils
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.MegaByte
class RescanViewHolder(
class BooksViewHolder(
override val containerView: View,
val bookUtils: BookUtils
private val bookUtils: BookUtils
) : ViewHolder(containerView),
LayoutContainer {
fun bind(
@ -37,7 +37,7 @@ class RescanViewHolder(
creator.setTextAndVisibility(book.creator)
publisher.setTextAndVisibility(book.publisher)
date.setTextAndVisibility(book.date)
size.setTextAndVisibility(book.size)
size.setTextAndVisibility(MegaByte(book.size).humanReadable)
language.text = bookUtils.getLanguage(book.getLanguage())
fileName.text = NetworkUtils.parseURL(
KiwixApplication.getInstance(), book.file.path
@ -52,13 +52,6 @@ class RescanViewHolder(
return@setOnLongClickListener true
}
}
private fun TextView.setTextAndVisibility(title: String?) =
if (title != null && title.isNotEmpty()) {
text = title
visibility = VISIBLE
} else {
visibility = GONE
}
}

View File

@ -10,15 +10,15 @@ import org.kiwix.kiwixmobile.utils.files.FileSearch.ResultListener
import javax.inject.Inject
class StorageObserver @Inject constructor(
val context: Context,
val sharedPreferenceUtil: SharedPreferenceUtil
private val context: Context,
private val sharedPreferenceUtil: SharedPreferenceUtil
) {
private val _booksOnFileSystem = PublishProcessor.create<Collection<Book>>()
val booksOnFileSystem = _booksOnFileSystem.distinctUntilChanged()
.doOnSubscribe { scanFiles() }
fun scanFiles() {
private fun scanFiles() {
FileSearch(context, object : ResultListener {
val foundBooks = mutableSetOf<Book>()
override fun onBookFound(book: Book) {

View File

@ -63,8 +63,8 @@ class ZimFileSelectFragment : BaseFragment() {
private lateinit var zimManageViewModel: ZimManageViewModel
private val rescanAdapter: RescanDataAdapter by lazy {
RescanDataAdapter(
private val booksAdapter: BooksAdapter by lazy {
BooksAdapter(
bookUtils, this::open, this::tryToDelete
)
}
@ -92,12 +92,12 @@ class ZimFileSelectFragment : BaseFragment() {
.get(ZimManageViewModel::class.java)
zim_swiperefresh.setOnRefreshListener(this::requestFileSystemCheck)
zimfilelist.run {
adapter = rescanAdapter
adapter = booksAdapter
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
setHasFixedSize(true)
}
zimManageViewModel.bookItems.observe(this, Observer {
rescanAdapter.itemList = it!!
booksAdapter.itemList = it!!
checkEmpty(it)
})
zimManageViewModel.deviceListIsRefreshing.observe(this, Observer {
@ -151,7 +151,7 @@ class ZimFileSelectFragment : BaseFragment() {
}
private fun requestFileSystemCheck() {
zimManageViewModel.checkFileSystem.onNext(Unit)
zimManageViewModel.requestFileSystemCheck.onNext(Unit)
}
private fun open(it: Book) {

View File

@ -1,350 +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.zim_manager.library_view;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.app.FragmentManager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import butterknife.BindView;
import butterknife.ButterKnife;
import eu.mhutti1.utils.storage.StorageDevice;
import eu.mhutti1.utils.storage.support.StorageSelectDialog;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import javax.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kiwix.kiwixmobile.KiwixMobileActivity;
import org.kiwix.kiwixmobile.R;
import org.kiwix.kiwixmobile.base.BaseFragment;
import org.kiwix.kiwixmobile.di.components.ActivityComponent;
import org.kiwix.kiwixmobile.di.components.ApplicationComponent;
import org.kiwix.kiwixmobile.downloader.DownloadService;
import org.kiwix.kiwixmobile.downloader.Downloader;
import org.kiwix.kiwixmobile.library.LibraryAdapter;
import org.kiwix.kiwixmobile.utils.NetworkUtils;
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil;
import org.kiwix.kiwixmobile.utils.StyleUtils;
import org.kiwix.kiwixmobile.utils.TestingUtils;
import org.kiwix.kiwixmobile.zim_manager.ZimManageActivity;
import static android.view.View.GONE;
import static org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book;
public class LibraryFragment extends BaseFragment
implements AdapterView.OnItemClickListener, StorageSelectDialog.OnSelectListener, LibraryViewCallback {
@BindView(R.id.library_list)
ListView libraryList;
@BindView(R.id.network_permission_text)
TextView networkText;
@BindView(R.id.network_permission_button)
Button permissionButton;
public LinearLayout llLayout;
@BindView(R.id.library_swiperefresh)
SwipeRefreshLayout swipeRefreshLayout;
private ArrayList<Book> books = new ArrayList<>();
public static DownloadService mService = new DownloadService();
public LibraryAdapter libraryAdapter;
@Inject
ConnectivityManager conMan;
@Inject
Downloader downloader;
private ZimManageActivity faActivity;
public static NetworkBroadcastReceiver networkBroadcastReceiver;
public static List<Book> downloadingBooks = new ArrayList<>();
public static boolean isReceiverRegistered = false;
@Inject
LibraryPresenter presenter;
@Inject
SharedPreferenceUtil sharedPreferenceUtil;
@Override
public void inject(@NotNull ActivityComponent activityComponent) {
activityComponent.inject(this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
TestingUtils.bindResource(LibraryFragment.class);
llLayout = (LinearLayout) inflater.inflate(R.layout.activity_library, container, false);
ButterKnife.bind(this, llLayout);
presenter.attachView(this);
networkText = llLayout.findViewById(R.id.network_text);
faActivity = (ZimManageActivity) super.getActivity();
swipeRefreshLayout.setOnRefreshListener(() -> refreshFragment());
libraryAdapter = new LibraryAdapter(super.getContext());
libraryList.setAdapter(libraryAdapter);
NetworkInfo network = conMan.getActiveNetworkInfo();
if (network == null || !network.isConnected()) {
displayNoNetworkConnection();
}
networkBroadcastReceiver = new NetworkBroadcastReceiver();
faActivity.registerReceiver(networkBroadcastReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
isReceiverRegistered = true;
presenter.loadRunningDownloadsFromDb();
return llLayout;
}
@Override
public void onStop() {
if (isReceiverRegistered) {
faActivity.unregisterReceiver(networkBroadcastReceiver);
isReceiverRegistered = false;
}
super.onStop();
}
@Override
public void showBooks(LinkedList<Book> books) {
if (books == null) {
displayNoItemsAvailable();
return;
}
Log.i("kiwix-showBooks", "Contains:" + books.size());
libraryAdapter.setAllBooks(books);
if (faActivity.searchView != null) {
libraryAdapter.getFilter().filter(
faActivity.searchView.getQuery(),
i -> stopScanningContent());
} else {
libraryAdapter.getFilter().filter("", i -> stopScanningContent());
}
libraryAdapter.notifyDataSetChanged();
libraryList.setOnItemClickListener(this);
}
@Override
public void displayNoNetworkConnection() {
if (books.size() != 0) {
Toast.makeText(super.getActivity(), R.string.no_network_connection, Toast.LENGTH_LONG).show();
return;
}
networkText.setText(R.string.no_network_connection);
networkText.setVisibility(View.VISIBLE);
permissionButton.setVisibility(GONE);
swipeRefreshLayout.setRefreshing(false);
swipeRefreshLayout.setEnabled(false);
libraryList.setVisibility(View.INVISIBLE);
TestingUtils.unbindResource(LibraryFragment.class);
}
@Override
public void displayNoItemsFound() {
networkText.setText(R.string.no_items_msg);
networkText.setVisibility(View.VISIBLE);
permissionButton.setVisibility(GONE);
swipeRefreshLayout.setRefreshing(false);
TestingUtils.unbindResource(LibraryFragment.class);
}
@Override
public void displayNoItemsAvailable() {
if (books.size() != 0) {
Toast.makeText(super.getActivity(), R.string.no_items_available, Toast.LENGTH_LONG).show();
return;
}
networkText.setText(R.string.no_items_available);
networkText.setVisibility(View.VISIBLE);
permissionButton.setVisibility(View.GONE);
swipeRefreshLayout.setRefreshing(false);
TestingUtils.unbindResource(LibraryFragment.class);
}
@Override
public void displayScanningContent() {
if (!swipeRefreshLayout.isRefreshing()) {
networkText.setVisibility(GONE);
permissionButton.setVisibility(GONE);
swipeRefreshLayout.setEnabled(true);
swipeRefreshLayout.setRefreshing(true);
TestingUtils.bindResource(LibraryFragment.class);
}
}
@Override
public void stopScanningContent() {
networkText.setVisibility(GONE);
permissionButton.setVisibility(GONE);
swipeRefreshLayout.setRefreshing(false);
TestingUtils.unbindResource(LibraryFragment.class);
}
public void refreshFragment() {
NetworkInfo network = conMan.getActiveNetworkInfo();
if (network == null || !network.isConnected()) {
Toast.makeText(super.getActivity(), R.string.no_network_connection, Toast.LENGTH_LONG).show();
swipeRefreshLayout.setRefreshing(false);
return;
}
networkBroadcastReceiver.onReceive(super.getActivity(), null);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (!libraryAdapter.isDivider(position)) {
if (getSpaceAvailable()
< Long.parseLong(((Book) (parent.getAdapter().getItem(position))).getSize()) * 1024f) {
Toast.makeText(super.getActivity(), getString(R.string.download_no_space)
+ "\n" + getString(R.string.space_available) + " "
+ LibraryUtils.bytesToHuman(getSpaceAvailable()), Toast.LENGTH_LONG).show();
Snackbar snackbar = Snackbar.make(libraryList,
getString(R.string.download_change_storage),
Snackbar.LENGTH_LONG)
.setAction(getString(R.string.open), v -> {
FragmentManager fm = getFragmentManager();
StorageSelectDialog dialogFragment = new StorageSelectDialog();
Bundle b = new Bundle();
b.putString(StorageSelectDialog.STORAGE_DIALOG_INTERNAL, getResources().getString(R.string.internal_storage));
b.putString(StorageSelectDialog.STORAGE_DIALOG_EXTERNAL, getResources().getString(R.string.external_storage));
b.putInt(StorageSelectDialog.STORAGE_DIALOG_THEME, StyleUtils.dialogStyle());
dialogFragment.setArguments(b);
dialogFragment.setOnSelectListener(this);
dialogFragment.show(fm, getResources().getString(R.string.pref_storage));
});
snackbar.setActionTextColor(Color.WHITE);
snackbar.show();
return;
}
// if (DownloadFragment.mDownloadFiles
// .containsValue(KIWIX_ROOT + StorageUtils.getFileNameFromUrl(((Book) parent.getAdapter()
// .getItem(position)).getUrl()))) {
// Toast.makeText(super.getActivity(), getString(R.string.zim_already_downloading), Toast.LENGTH_LONG)
// .show();
// } else {
//
// NetworkInfo network = conMan.getActiveNetworkInfo();
// if (network == null || !network.isConnected()) {
// Toast.makeText(super.getActivity(), getString(R.string.no_network_connection), Toast.LENGTH_LONG)
// .show();
// return;
// }
if (KiwixMobileActivity.wifiOnly && !NetworkUtils.isWiFi(getContext())) {
new AlertDialog.Builder(getContext())
.setTitle(R.string.wifi_only_title)
.setMessage(R.string.wifi_only_msg)
.setPositiveButton(R.string.yes, (dialog, i) -> {
sharedPreferenceUtil.putPrefWifiOnly(false);
KiwixMobileActivity.wifiOnly = false;
downloadFile((Book) parent.getAdapter().getItem(position));
})
.setNegativeButton(R.string.no, (dialog, i) -> {
})
.show();
} else {
downloadFile((Book) parent.getAdapter().getItem(position));
}
}
}
@Override
public void downloadFile(Book book) {
downloader.download(book);
// downloadingBooks.add(book);
// if (libraryAdapter != null && faActivity != null && faActivity.searchView != null) {
// libraryAdapter.getFilter().filter(faActivity.searchView.getQuery());
// }
// Toast.makeText(super.getActivity(), getString(R.string.download_started_library), Toast.LENGTH_LONG)
// .show();
//
// ZimManageActivity manage = (ZimManageActivity) super.getActivity();
// manage.displayDownloadInterface();
}
public long getSpaceAvailable() {
return new File(sharedPreferenceUtil.getPrefStorage()).getFreeSpace();
}
@Override
public void selectionCallback(StorageDevice storageDevice) {
sharedPreferenceUtil.putPrefStorage(storageDevice.getName());
if (storageDevice.isInternal()) {
sharedPreferenceUtil.putPrefStorageTitle(getResources().getString(R.string.internal_storage));
} else {
sharedPreferenceUtil.putPrefStorageTitle(getResources().getString(R.string.external_storage));
}
}
public class NetworkBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
NetworkInfo network = conMan.getActiveNetworkInfo();
if (network == null || !network.isConnected()) {
displayNoNetworkConnection();
}
if ((books == null || books.isEmpty()) && network != null && network.isConnected()) {
presenter.loadBooks();
permissionButton.setVisibility(GONE);
networkText.setVisibility(GONE);
libraryList.setVisibility(View.VISIBLE);
}
}
}
}

View File

@ -0,0 +1,232 @@
/*
* 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.library_view
import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProvider
import android.arch.lifecycle.ViewModelProviders
import android.content.Context
import android.net.ConnectivityManager
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import eu.mhutti1.utils.storage.StorageDevice
import eu.mhutti1.utils.storage.support.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
import org.kiwix.kiwixmobile.KiwixMobileActivity
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.R.string
import org.kiwix.kiwixmobile.base.BaseFragment
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.library.entity.LibraryNetworkEntity.Book
import org.kiwix.kiwixmobile.utils.BookUtils
import org.kiwix.kiwixmobile.utils.DialogShower
import org.kiwix.kiwixmobile.utils.KiwixDialog.YesNoDialog.WifiOnly
import org.kiwix.kiwixmobile.utils.NetworkUtils
import org.kiwix.kiwixmobile.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.utils.StyleUtils
import org.kiwix.kiwixmobile.utils.TestingUtils
import org.kiwix.kiwixmobile.zim_manager.NetworkState
import org.kiwix.kiwixmobile.zim_manager.NetworkState.CONNECTED
import org.kiwix.kiwixmobile.zim_manager.NetworkState.NOT_CONNECTED
import org.kiwix.kiwixmobile.zim_manager.ZimManageViewModel
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryAdapter
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryDelegate.BookDelegate
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryDelegate.DividerDelegate
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem
import java.io.File
import javax.inject.Inject
class LibraryFragment : BaseFragment() {
@Inject lateinit var conMan: ConnectivityManager
@Inject lateinit var downloader: Downloader
@Inject lateinit var sharedPreferenceUtil: SharedPreferenceUtil
@Inject lateinit var dialogShower: DialogShower
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
@Inject lateinit var bookUtils: BookUtils
private lateinit var zimManageViewModel: ZimManageViewModel
private val libraryAdapter: LibraryAdapter by lazy {
LibraryAdapter(
delegates = *arrayOf(BookDelegate(bookUtils, this::onBookItemClick), DividerDelegate)
)
}
private val spaceAvailable: Long
get() = File(sharedPreferenceUtil.prefStorage).freeSpace
private val noWifiWithWifiOnlyPreferenceSet
get() = sharedPreferenceUtil.prefWifiOnly && !NetworkUtils.isWiFi(context!!)
private val isNotConnected get() = conMan.activeNetworkInfo?.isConnected?.not() ?: true
override fun inject(activityComponent: ActivityComponent) {
activityComponent.inject(this)
}
override fun onAttach(context: Context?) {
super.onAttach(context)
zimManageViewModel = ViewModelProviders.of(activity!!, viewModelFactory)
.get(ZimManageViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
TestingUtils.bindResource(LibraryFragment::class.java)
return inflater.inflate(R.layout.activity_library, container, false)
}
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
librarySwipeRefresh.setOnRefreshListener { refreshFragment() }
libraryList.run {
adapter = libraryAdapter
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
setHasFixedSize(true)
}
zimManageViewModel.libraryItems.observe(this, Observer(this::onLibraryItemsChange))
zimManageViewModel.libraryListIsRefreshing.observe(
this, Observer(this::onRefreshStateChange)
)
zimManageViewModel.networkStates.observe(this, Observer(this::onNetworkStateChange))
}
private fun onRefreshStateChange(isRefreshing: Boolean?) {
librarySwipeRefresh.isRefreshing = isRefreshing!!
}
private fun onNetworkStateChange(networkState: NetworkState?) {
when (networkState) {
CONNECTED -> {
}
NOT_CONNECTED -> {
if (libraryAdapter.itemCount > 0) {
context.toast(R.string.no_network_connection)
} else {
libraryErrorText.setText(R.string.no_network_connection)
libraryErrorText.visibility = VISIBLE
}
}
}
}
private fun onLibraryItemsChange(it: List<LibraryListItem>?) {
libraryAdapter.itemList = it!!
if (it.isEmpty()) {
libraryErrorText.setText(
if (isNotConnected) R.string.no_network_connection
else R.string.no_items_msg
)
libraryErrorText.visibility = VISIBLE
TestingUtils.unbindResource(LibraryFragment::class.java)
} else {
libraryErrorText.visibility = GONE
}
}
private fun refreshFragment() {
if (isNotConnected) {
context.toast(R.string.no_network_connection)
} else {
zimManageViewModel.requestDownloadLibrary.onNext(Unit)
}
}
private fun downloadFile(book: Book) {
downloader.download(book)
}
private fun storeDeviceInPreferences(storageDevice: StorageDevice) {
sharedPreferenceUtil.putPrefStorage(storageDevice.name)
sharedPreferenceUtil.putPrefStorageTitle(
getString(
if (storageDevice.isInternal) R.string.internal_storage
else R.string.external_storage
)
)
}
private fun onBookItemClick(item: BookItem) {
when {
notEnoughSpaceAvailable(item) -> {
context.toast(
getString(R.string.download_no_space)
+ "\n" + getString(R.string.space_available) + " "
+ LibraryUtils.bytesToHuman(spaceAvailable)
)
libraryList.snack(
R.string.download_change_storage,
R.string.open,
this::showStorageSelectDialog
)
return
}
isNotConnected -> {
context.toast(R.string.no_network_connection)
return
}
noWifiWithWifiOnlyPreferenceSet -> {
dialogShower.show(WifiOnly, {
sharedPreferenceUtil.putPrefWifiOnly(false)
KiwixMobileActivity.wifiOnly = false
downloadFile(item.book)
})
return
}
else -> downloadFile(item.book)
}
}
private fun notEnoughSpaceAvailable(item: BookItem) =
spaceAvailable < item.book.size.toLong() * 1024f
private fun showStorageSelectDialog() {
val dialogFragment = StorageSelectDialog()
dialogFragment.arguments = Bundle().apply {
putString(
StorageSelectDialog.STORAGE_DIALOG_INTERNAL,
getString(string.internal_storage)
)
putString(
StorageSelectDialog.STORAGE_DIALOG_EXTERNAL,
getString(string.external_storage)
)
putInt(StorageSelectDialog.STORAGE_DIALOG_THEME, StyleUtils.dialogStyle())
}
dialogFragment.setOnSelectListener(this::storeDeviceInPreferences)
dialogFragment.show(fragmentManager, getString(string.pref_storage))
}
}

View File

@ -1,64 +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.zim_manager.library_view;
import android.util.Log;
import io.reactivex.android.schedulers.AndroidSchedulers;
import javax.inject.Inject;
import org.kiwix.kiwixmobile.base.BasePresenter;
import org.kiwix.kiwixmobile.database.BookDao;
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity;
import org.kiwix.kiwixmobile.network.KiwixService;
/**
* Created by EladKeyshawn on 06/04/2017.
*/
public class LibraryPresenter extends BasePresenter<LibraryViewCallback> {
@Inject
KiwixService kiwixService;
@Inject
BookDao bookDao;
@Inject
public LibraryPresenter() {
}
void loadBooks() {
getMvpView().displayScanningContent();
kiwixService.getLibrary()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(library -> getMvpView().showBooks(library.getBooks()), error -> {
String msg = error.getLocalizedMessage();
Log.w("kiwixLibrary", "Error loading books:" + (msg != null ? msg : "(null)"));
getMvpView().displayNoItemsFound();
});
}
void loadRunningDownloadsFromDb() {
for (LibraryNetworkEntity.Book book : bookDao.getDownloadingBooks()) {
// if (!DownloadFragment.mDownloads.containsValue(book)) {
book.url = book.remoteUrl;
getMvpView().downloadFile(book);
// }
}
}
}

View File

@ -0,0 +1,38 @@
package org.kiwix.kiwixmobile.zim_manager.library_view.adapter
import android.support.v7.widget.RecyclerView
import android.view.ViewGroup
/*
* 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/>.
*/
interface AbsDelegateAdapter<INSTANCE : SUPERTYPE, SUPERTYPE, VIEWHOLDER : RecyclerView.ViewHolder> : AdapterDelegate<SUPERTYPE> {
override fun bind(
viewHolder: RecyclerView.ViewHolder,
itemToBind: SUPERTYPE
) {
onBindViewHolder(itemToBind as INSTANCE, viewHolder as VIEWHOLDER)
}
override fun createViewHolder(parent: ViewGroup): VIEWHOLDER
fun onBindViewHolder(
item: INSTANCE,
holder: VIEWHOLDER
)
}

View File

@ -0,0 +1,16 @@
package org.kiwix.kiwixmobile.zim_manager.library_view.adapter
import android.support.v7.widget.RecyclerView.ViewHolder
import android.view.ViewGroup
interface AdapterDelegate<T> {
fun createViewHolder(parent: ViewGroup): ViewHolder
fun bind(
viewHolder: ViewHolder,
itemToBind: T
)
fun isFor(item: T): Boolean
}

View File

@ -0,0 +1,38 @@
package org.kiwix.kiwixmobile.zim_manager.library_view.adapter
import android.support.v4.util.SparseArrayCompat
import android.support.v7.widget.RecyclerView
import android.view.ViewGroup
class AdapterDelegateManager<T>() {
fun addDelegate(delegate: AdapterDelegate<T>) {
delegates.put(delegates.size(), delegate)
}
fun createViewHolder(
parent: ViewGroup,
viewType: Int
) = delegates[viewType].createViewHolder(parent)
fun onBindViewHolder(
libraryListItem: T,
holder: RecyclerView.ViewHolder
) {
delegates[holder.itemViewType].bind(holder, libraryListItem)
}
fun getViewTypeFor(item: T) = delegates.keyAt(getDelegateIndexFor(item))
private fun getDelegateIndexFor(item: T): Int {
for (index in 0..delegates.size()) {
val valueAt = delegates.valueAt(index)
if (valueAt.isFor(item)) {
return index;
}
}
throw RuntimeException("No delegate registered for $item")
}
var delegates: SparseArrayCompat<AdapterDelegate<T>> = SparseArrayCompat()
}

View File

@ -0,0 +1,24 @@
package org.kiwix.kiwixmobile.zim_manager.library_view.adapter
import java.util.Locale
class Language constructor(
locale: Locale,
var active: Boolean?,
var language: String = locale.displayLanguage,
var languageLocalized: String = locale.getDisplayLanguage(locale),
var languageCode: String = locale.isO3Language,
var languageCodeISO2: String = locale.language
)
{
constructor(
languageCode: String,
active: Boolean?
) : this(Locale(languageCode), active) {
}
override fun equals(obj: Any?): Boolean {
return (obj as Language).language == language && obj.active == active
}
}

View File

@ -0,0 +1,82 @@
/*
* 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.zim_manager.library_view.adapter
import android.support.v7.widget.RecyclerView
import android.view.ViewGroup
class LibraryAdapter(
private val delegateManager: AdapterDelegateManager<LibraryListItem> = AdapterDelegateManager(),
vararg delegates: AdapterDelegate<LibraryListItem>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
init {
delegates.forEach {
delegateManager.addDelegate(it)
}
setHasStableIds(true)
}
var itemList: List<LibraryListItem> = mutableListOf()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
) = delegateManager.createViewHolder(parent, viewType)
override fun getItemCount() = itemList.size
override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int
) {
delegateManager.onBindViewHolder(itemList[position], holder)
}
override fun getItemId(position: Int) = itemList[position].id
override fun getItemViewType(position: Int) =
delegateManager.getViewTypeFor(itemList[position])
}
//private Observable<Book> getMatches(Book b, String s) {
// StringBuilder text = new StringBuilder();
// text.append(b.getTitle()).append("|").append(b.getDescription()).append("|")
// .append(parseURL(context, b.getUrl())).append("|");
// if (bookUtils.localeMap.containsKey(b.getLanguage())) {
// text.append(bookUtils.localeMap.get(b.getLanguage()).getDisplayLanguage()).append("|");
// }
// String[] words = s.toLowerCase().split("\\s+");
// b.searchMatches = Observable.fromArray(words)
// .filter(text.toString().toLowerCase()::contains)
// .count()
// .blockingGet()
// .intValue();
// if (b.searchMatches > 0) {
// return Observable.just(b);
// } else {
// return Observable.empty();
// }
//}}

View File

@ -0,0 +1,68 @@
/*
* 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.library_view.adapter
import android.view.ViewGroup
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.R.layout
import org.kiwix.kiwixmobile.extensions.inflate
import org.kiwix.kiwixmobile.utils.BookUtils
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.DividerItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryViewHolder.LibraryBookViewHolder
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryViewHolder.LibraryDividerViewHolder
sealed class LibraryDelegate<I : LibraryListItem, VH : LibraryViewHolder<I>> :
AbsDelegateAdapter<I, LibraryListItem, VH> {
class BookDelegate(
private val bookUtils: BookUtils,
private val clickAction: (BookItem) -> Unit
) : LibraryDelegate<BookItem, LibraryBookViewHolder>() {
override fun createViewHolder(parent: ViewGroup) =
LibraryBookViewHolder(
parent.inflate(R.layout.library_item, false),
bookUtils,
clickAction
)
override fun onBindViewHolder(
item: BookItem,
holder: LibraryBookViewHolder
) {
holder.bind(item)
}
override fun isFor(item: LibraryListItem) = item is BookItem
}
object DividerDelegate : LibraryDelegate<DividerItem, LibraryDividerViewHolder>() {
override fun createViewHolder(parent: ViewGroup) =
LibraryDividerViewHolder(parent.inflate(layout.library_divider, false))
override fun onBindViewHolder(
item: DividerItem,
holder: LibraryDividerViewHolder
) {
holder.bind(item)
}
override fun isFor(item: LibraryListItem) = item is DividerItem
}
}

View File

@ -0,0 +1,17 @@
package org.kiwix.kiwixmobile.zim_manager.library_view.adapter
import org.kiwix.kiwixmobile.library.entity.LibraryNetworkEntity.Book
sealed class LibraryListItem() {
abstract val id: Long
data class DividerItem constructor(
override val id: Long,
val text: String
) : LibraryListItem()
data class BookItem(
val book: Book,
override val id: Long = book.id.hashCode().toLong()
) : LibraryListItem()
}

View File

@ -0,0 +1,63 @@
package org.kiwix.kiwixmobile.zim_manager.library_view.adapter
import android.support.v7.widget.RecyclerView
import android.view.View
import butterknife.internal.DebouncingOnClickListener
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.library_divider.divider_text
import kotlinx.android.synthetic.main.library_item.creator
import kotlinx.android.synthetic.main.library_item.date
import kotlinx.android.synthetic.main.library_item.description
import kotlinx.android.synthetic.main.library_item.favicon
import kotlinx.android.synthetic.main.library_item.fileName
import kotlinx.android.synthetic.main.library_item.language
import kotlinx.android.synthetic.main.library_item.publisher
import kotlinx.android.synthetic.main.library_item.size
import kotlinx.android.synthetic.main.library_item.title
import org.kiwix.kiwixmobile.KiwixApplication
import org.kiwix.kiwixmobile.downloader.model.Base64String
import org.kiwix.kiwixmobile.extensions.setBitmap
import org.kiwix.kiwixmobile.extensions.setTextAndVisibility
import org.kiwix.kiwixmobile.utils.BookUtils
import org.kiwix.kiwixmobile.utils.NetworkUtils
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.BookItem
import org.kiwix.kiwixmobile.zim_manager.library_view.adapter.LibraryListItem.DividerItem
sealed class LibraryViewHolder<T : LibraryListItem>(override val containerView: View) : RecyclerView.ViewHolder(
containerView
), LayoutContainer {
abstract fun bind(item: T)
class LibraryBookViewHolder(
view: View,
private val bookUtils: BookUtils,
private val clickAction: (BookItem) -> Unit
) : LibraryViewHolder<BookItem>(view) {
override fun bind(item: BookItem) {
title.setTextAndVisibility(item.book.title)
description.setTextAndVisibility(item.book.description)
creator.setTextAndVisibility(item.book.creator)
publisher.setTextAndVisibility(item.book.publisher)
date.setTextAndVisibility(item.book.date)
size.setTextAndVisibility(MegaByte(item.book.size).humanReadable)
language.text = bookUtils.getLanguage(item.book.getLanguage())
fileName.text = NetworkUtils.parseURL(
KiwixApplication.getInstance(), item.book.file?.path ?: ""
)
favicon.setBitmap(Base64String(item.book.favicon))
containerView.setOnClickListener {
clickAction.invoke(item)
}
}
}
class LibraryDividerViewHolder(view: View) : LibraryViewHolder<DividerItem>(view) {
override fun bind(item: DividerItem) {
divider_text.text = item.text
}
}
}

View File

@ -0,0 +1,16 @@
package org.kiwix.kiwixmobile.zim_manager.library_view.adapter
import java.text.DecimalFormat
inline class MegaByte(val megabyteString: String?) {
val humanReadable
get() = megabyteString?.toLongOrNull()?.let {
val units = arrayOf("KB", "MB", "GB", "TB")
val conversion = (Math.log10(it.toDouble()) / Math.log10(1024.0)).toInt()
(DecimalFormat("#,##0.#")
.format(it / Math.pow(1024.0, conversion.toDouble()))
+ " "
+ units[conversion])
} ?: ""
}

View File

@ -1,61 +1,42 @@
<?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"
tools:context=".zim_manager.library_view.LibraryFragment">
<RelativeLayout
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/listBackground">
android:animateLayoutChanges="true"
tools:context=".zim_manager.library_view.LibraryFragment"
>
<TextView
android:id="@+id/network_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingBottom="@dimen/library_textview_padding"
android:textSize="@dimen/library_font_size"
android:visibility="gone" />
<TextView
android:id="@+id/network_permission_text"
<TextView
android:id="@+id/libraryErrorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/network_permission_text_margin_top"
android:text="@string/get_library_over_network"
android:visibility="gone" />
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<Button
android:id="@+id/network_permission_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/network_permission_button_margin_top"
android:text="@string/proceed"
android:visibility="gone" />
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/library_swiperefresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/librarySwipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
>
<ListView
android:id="@+id/library_list"
<android.support.v7.widget.RecyclerView
android:id="@+id/libraryList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/listBackground"
android:divider="@null"
android:paddingBottom="@dimen/library_article_list_padding" />
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.v4.widget.SwipeRefreshLayout>
</RelativeLayout>
</LinearLayout>
</android.support.constraint.ConstraintLayout>

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:orientation="vertical"
android:animateLayoutChanges="true"
>
@ -11,7 +10,7 @@
<TextView
android:id="@+id/download_management_no_downloads"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:text="@string/no_downloads_here"
android:textSize="@dimen/download_management_no_downloads_text_size"
android:visibility="gone"
@ -26,6 +25,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>
</android.support.constraint.ConstraintLayout>

View File

@ -1,109 +1,124 @@
<?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="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:minHeight="?android:attr/listPreferredItemHeight"
android:orientation="horizontal"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
>
<ImageView
android:id="@+id/favicon"
android:layout_width="@dimen/favicon_width"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginRight="@dimen/favicon_margin_right"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@mipmap/kiwix_icon" />
android:id="@+id/favicon"
android:layout_width="@dimen/favicon_width"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginRight="@dimen/favicon_margin_right"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:src="@mipmap/kiwix_icon"
/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingLeft="@dimen/dimen_small_padding"
tools:ignore="HardcodedText">
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:paddingLeft="@dimen/dimen_small_padding"
tools:ignore="HardcodedText"
>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title"
android:textAppearance="?android:attr/textAppearanceListItem" />
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title"
android:textAppearance="?android:attr/textAppearanceListItem"
/>
<TextView
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Description"
android:textAppearance="?android:attr/textAppearanceSmall" />
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Description"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="@dimen/dimen_medium_padding">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="@dimen/dimen_medium_padding"
>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
>
<TextView
android:id="@+id/size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="File Size"
android:textAppearance="?android:attr/textAppearanceSmall" />
android:id="@+id/size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="File Size"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/creator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Author"
android:textAppearance="?android:attr/textAppearanceSmall" />
android:id="@+id/creator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Author"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/publisher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Publisher"
android:textAppearance="?android:attr/textAppearanceSmall" />
android:id="@+id/publisher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Publisher"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="right"
android:orientation="vertical">
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="right"
android:orientation="vertical"
>
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Date"
android:textAppearance="?android:attr/textAppearanceSmall" />
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Date"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/language"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Language"
android:textAppearance="?android:attr/textAppearanceSmall" />
android:id="@+id/language"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Language"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
<TextView
android:id="@+id/fileName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="File Name"
android:textAppearance="?android:attr/textAppearanceSmall" />
android:id="@+id/fileName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="File Name"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
</LinearLayout>

View File

@ -11,11 +11,7 @@
<TextView
android:id="@+id/file_management_no_files"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:orientation="vertical"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/file_management_no_files_padding_bottom"
android:text="@string/no_files_here"
android:textSize="@dimen/file_management_no_files_text_size"
@ -37,8 +33,7 @@
android:id="@+id/zimfilelist"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@null"
android:paddingBottom="@dimen/zimfilelist_padding_bottom"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
/>
</android.support.v4.widget.SwipeRefreshLayout>

View File

@ -1,51 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/zim_manager_main_activity"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".zim_manager.ZimManageActivity">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/zim_manager_main_activity"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".zim_manager.ZimManageActivity"
>
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/dimen_medium_padding"
android:theme="@style/AppTheme.AppBarOverlay">
<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:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
app:layout_scrollFlags="scroll|enterAlways">
android:layout_height="wrap_content"
android:paddingTop="@dimen/dimen_medium_padding"
android:theme="@style/AppTheme.AppBarOverlay"
>
<RelativeLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay">
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|snap|enterAlways"
app:popupTheme="@style/AppTheme.PopupOverlay"
>
</android.support.v7.widget.Toolbar>
</RelativeLayout>
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="fill"
app:tabMaxWidth="0dp" />
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="fill"
app:tabMaxWidth="0dp"
/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior"
/>
</android.support.design.widget.CoordinatorLayout>

View File

@ -14,16 +14,13 @@
<dimen name="abc_dropdownitem_text_padding_right">8dip</dimen>
<dimen name="library_textview_padding">75dp</dimen>
<dimen name="library_font_size">20sp</dimen>
<dimen name="library_article_list_padding">60dp</dimen>
<dimen name="network_permission_text_margin_top">200dp</dimen>
<dimen name="network_permission_button_margin_top">230dp</dimen>
<dimen name="bookmark_title_text_size">15sp</dimen>
<dimen name="download_management_no_downloads_padding_bottom">75dp</dimen>
<dimen name="help_cardview_card_elevation">2dp</dimen>
<dimen name="kiwix_search_widget_layout_id_height">50dp</dimen>
<dimen name="file_management_no_files_padding_bottom">75dp</dimen>
<dimen name="file_management_no_files_text_size">20sp</dimen>
<dimen name="zimfilelist_padding_bottom">60dp</dimen>
<dimen name="webview_search_edit_text_margin_right">10dip</dimen>
<dimen name="progressbar_layout_margin_top">300dp</dimen>
<dimen name="fullscreen_control_button_margin">7dp</dimen>

View File

@ -14,7 +14,7 @@ buildscript {
}
ext {
supportLibraryVersion = '27.0.2'
supportLibraryVersion = '27.1.1'
rxJavaVersion = '2.1.9'
rxAndroidVersion = '2.0.2'
okHttpVersion = '3.9.1'