Compare commits

..

48 Commits

Author SHA1 Message Date
0a31c223e3 Don't lint release builds 2016-01-25 15:15:47 +01:00
0f42956f3f Update readme 2016-01-25 14:01:37 +01:00
ac2485d4a7 Release 0.1.2 2016-01-25 13:56:58 +01:00
7993ec5074 Make toolbar always visible 2016-01-25 13:54:23 +01:00
4521174138 Fix layout overlapping 2016-01-25 13:43:21 +01:00
27b95e9d73 Minor changes 2016-01-25 13:19:03 +01:00
a54425f47d Merge pull request #69 from icewind1991/info-show-source
Show manga source in info panel
2016-01-25 13:01:44 +01:00
4918e67fda Show manga source in info panel 2016-01-25 12:49:56 +01:00
b174adbab0 Use a gradient at the bottom of the cover. Remove external repositories from gradle 2016-01-24 23:41:21 +01:00
59cc87c583 Fix #58 and #59 2016-01-24 13:57:20 +01:00
0e87dc995a Add backpressure buffer for downloads 2016-01-24 13:23:29 +01:00
fad7b75b96 Place reload button above the image 2016-01-24 12:52:41 +01:00
c99c90fc4c Merge pull request #57 from icewind1991/chapter-list-ellipsize
elipsize chapter list in the middle
2016-01-24 12:42:49 +01:00
594219848d Fix number of simultaneous downloads ignored (again) 2016-01-24 12:37:41 +01:00
fa301bfbd2 elipsize chapter list in the middle 2016-01-24 12:15:43 +01:00
50306f6ea3 Merge pull request #53 from icewind1991/sort-order
save per-manga sort order
2016-01-24 00:10:02 +01:00
9b90ad0a3b save per-manga sort order 2016-01-24 00:01:24 +01:00
5c854984e4 Fix #52 2016-01-23 21:58:36 +01:00
6c844cfd9c Merge pull request #51 from icewind1991/last-page
Load the last page when switching to the previous chapter (Fix #48)
2016-01-23 19:13:21 +01:00
9e666dcdb3 Load the last page when switching to the previous chapter 2016-01-23 17:10:56 +01:00
e81f98a975 Fix an UI refresh issue 2016-01-23 14:17:01 +01:00
11dc0d7e9e Change filename for downloaded chapters, using the last path from the url is not reliable. This will break compatibility with previously downloaded chapters, they have to be deleted and downloaded again.
Disable download progress in the chapters view, it will avoid some crashes.
2016-01-23 13:58:53 +01:00
07ed2e2ebb Hold the same manga instance (allowing to refresh manga state from the catalogue). Other minor changes. 2016-01-22 20:22:16 +01:00
e1aa460106 Allow to display manga from catalogue as a simple list (#35) 2016-01-22 17:37:23 +01:00
75a77566cf Trying switches instead of checkboxes 2016-01-21 16:55:18 +01:00
dd0a2d842a Improve recent chapters layout 2016-01-21 16:38:25 +01:00
fa71e906c9 Change recent chapters query, now it shows last month updates. Download manager now uses a thread pool. 2016-01-21 02:26:34 +01:00
e6a17e25a9 Tint navigation bar on Lollipop and higher 2016-01-20 22:06:22 +01:00
d88513de56 Reenable recent updates tab 2016-01-20 19:43:44 +01:00
ad97d03f1d Change toolbar color (Fix #43). Allow to also remove from library (Fix #44). Rewrite RxPager. 2016-01-20 19:21:17 +01:00
7fc23d526b Update readme 2016-01-20 14:46:05 +01:00
0210fd8828 Fix a big issue with the download threads. Release 0.1.1 2016-01-20 14:38:45 +01:00
0332d8dd79 Fix #39 2016-01-19 21:07:32 +01:00
111ec5541f Fix an error with empty pages from downloaded chapters (images not found) 2016-01-19 19:30:55 +01:00
4bf15a5a2c Allow to mark all previous chapters as read 2016-01-19 16:35:36 +01:00
416fd128ba Upgrade StorIO to 1.8.0 2016-01-19 15:49:13 +01:00
dda0c50a1c Show only recent chapters of the library 2016-01-18 20:18:46 +01:00
f0a3c9c2dc Don't reset library adapter if it's not needed 2016-01-18 19:41:11 +01:00
8520a47286 Sort sources alphabetically. Fix #31 2016-01-18 19:28:13 +01:00
522e900b5a Initial support for recent updates. #20 2016-01-18 18:04:07 +01:00
b9bb41164f Merge pull request #33 from wopian/patch-2
Update readme
2016-01-18 00:23:57 +01:00
2b2fa0de2f Merge pull request #32 from wopian/patch-1
Fix some grammatical issues
2016-01-18 00:23:43 +01:00
e747083b06 Update README.md 2016-01-17 21:11:53 +00:00
e08dd95435 Fix some grammatical issues 2016-01-17 20:59:21 +00:00
173e86320b Allow to add a manga to the library with a long click 2016-01-17 16:11:30 +01:00
b2e579173b Update readme 2016-01-17 15:29:31 +01:00
79229d9c6a Prepare for dev releases 2016-01-17 15:03:04 +01:00
d25cbe9005 Update readme 2016-01-16 21:48:46 +01:00
97 changed files with 1278 additions and 423 deletions

View File

@ -1,4 +1,4 @@
Tachiyomi is a manga reader for Android free and open source.
Tachiyomi is a free and open source manga reader for Android.
Keep in mind it's still a beta, so expect it to crash sometimes.
@ -12,6 +12,11 @@ Current features:
* Schedule searching for updates
* Categories to organize your library
## Download
[![stable release](https://img.shields.io/badge/release-v0.1.2-blue.svg)](https://github.com/inorichi/tachiyomi/releases/download/v0.1.2/tachiyomi-v0.1.2.apk)
[![latest debug build](https://img.shields.io/badge/debug-latest%20build-blue.svg)](http://tachiyomi.kanade.eu/latest/app-debug.apk)
## License
Copyright 2015 Javier Tomás

View File

@ -39,8 +39,8 @@ android {
minSdkVersion 16
targetSdkVersion 23
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
versionCode 1
versionName "0.1.0"
versionCode 3
versionName "0.1.2"
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
@ -53,6 +53,9 @@ android {
}
buildTypes {
debug {
applicationIdSuffix ".debug"
}
release {
minifyEnabled true
shrinkResources true
@ -70,6 +73,7 @@ android {
lintOptions {
abortOnError false
checkReleaseBuilds false
}
}
@ -78,7 +82,7 @@ dependencies {
final SUPPORT_LIBRARY_VERSION = '23.1.1'
final DAGGER_VERSION = '2.0.2'
final MOCKITO_VERSION = '1.10.19'
final STORIO_VERSION = '1.7.0'
final STORIO_VERSION = '1.8.0'
final ICEPICK_VERSION = '3.1.0'
compile fileTree(dir: 'libs', include: ['*.jar'])
@ -90,6 +94,7 @@ dependencies {
compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION"
compile "com.android.support:percent:$SUPPORT_LIBRARY_VERSION"
compile 'com.squareup.okhttp:okhttp-urlconnection:2.7.2'
compile 'com.squareup.okhttp:okhttp:2.7.2'
compile 'com.squareup.okio:okio:1.6.0'
@ -111,9 +116,9 @@ dependencies {
compile "frankiesardo:icepick:$ICEPICK_VERSION"
provided "frankiesardo:icepick-processor:$ICEPICK_VERSION"
compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
compile 'eu.davidea:flexible-adapter:4.2.0@aar'
compile 'eu.davidea:flexible-adapter:4.2.0'
compile 'com.nononsenseapps:filepicker:2.5.1'
compile 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
compile 'com.github.amulyakhare:TextDrawable:558677e'
compile 'com.github.pwittchen:reactivenetwork:0.1.5'
compile "com.google.dagger:dagger:$DAGGER_VERSION"

View File

@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.injection.module.AppModule;
import timber.log.Timber;
@ReportsCrashes(
formUri = "http://mangafeed.kanade.eu/crash_report",
formUri = "http://tachiyomi.kanade.eu/crash_report",
reportType = org.acra.sender.HttpSender.Type.JSON,
httpMethod = org.acra.sender.HttpSender.Method.PUT,
excludeMatchingSharedPreferencesKeys={".*username.*",".*password.*"}

View File

@ -27,10 +27,12 @@ import eu.kanade.tachiyomi.data.database.models.ChapterSQLiteTypeMapping;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.database.models.MangaCategory;
import eu.kanade.tachiyomi.data.database.models.MangaCategorySQLiteTypeMapping;
import eu.kanade.tachiyomi.data.database.models.MangaChapter;
import eu.kanade.tachiyomi.data.database.models.MangaSQLiteTypeMapping;
import eu.kanade.tachiyomi.data.database.models.MangaSync;
import eu.kanade.tachiyomi.data.database.models.MangaSyncSQLiteTypeMapping;
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver;
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver;
import eu.kanade.tachiyomi.data.database.tables.CategoryTable;
import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable;
@ -160,23 +162,14 @@ public class DatabaseHelper {
.prepare();
}
public PreparedGetListOfObjects<Chapter> getChapters(long manga_id, boolean sortAToZ, boolean onlyUnread) {
Query.CompleteBuilder query = Query.builder()
.table(ChapterTable.TABLE)
.orderBy(ChapterTable.COLUMN_CHAPTER_NUMBER + (sortAToZ ? " ASC" : " DESC"));
if (onlyUnread) {
query = query.where(ChapterTable.COLUMN_MANGA_ID + "=? AND " + ChapterTable.COLUMN_READ + "=?")
.whereArgs(manga_id, 0);
} else {
query = query.where(ChapterTable.COLUMN_MANGA_ID + "=?")
.whereArgs(manga_id);
}
public PreparedGetListOfObjects<MangaChapter> getRecentChapters(Date date) {
return db.get()
.listOfObjects(Chapter.class)
.withQuery(query.build())
.listOfObjects(MangaChapter.class)
.withQuery(RawQuery.builder()
.query(MangaChapterGetResolver.getRecentChaptersQuery(date))
.observesTables(ChapterTable.TABLE)
.build())
.withGetResolver(MangaChapterGetResolver.INSTANCE)
.prepare();
}

View File

@ -6,9 +6,9 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.support.annotation.NonNull;
import eu.kanade.tachiyomi.data.database.tables.CategoryTable;
import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable;
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable;
import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
import eu.kanade.tachiyomi.data.database.tables.MangaTable;
public class DbOpenHelper extends SQLiteOpenHelper {

View File

@ -68,6 +68,10 @@ public class Manga implements Serializable {
public static final int COMPLETED = 2;
public static final int LICENSED = 3;
public static final int SORT_AZ = 0;
public static final int SORT_ZA = 1;
public static final int SORT_MASK = 1;
public Manga() {}
public static Manga create(String pathUrl) {
@ -120,6 +124,18 @@ public class Manga implements Serializable {
}
}
public void setFlags(int flag, int mask) {
chapter_flags = (chapter_flags & ~mask) | (flag & mask);
}
public boolean sortChaptersAZ () {
return (this.chapter_flags & SORT_MASK) == SORT_AZ;
}
public void setChapterOrder(int order) {
setFlags(order, SORT_MASK);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@ -0,0 +1,12 @@
package eu.kanade.tachiyomi.data.database.models;
public class MangaChapter {
public Manga manga;
public Chapter chapter;
public MangaChapter(Manga manga, Chapter chapter) {
this.manga = manga;
this.chapter = chapter;
}
}

View File

@ -5,8 +5,8 @@ import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
import java.io.Serializable;
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService;
import eu.kanade.tachiyomi.data.database.tables.MangaSyncTable;
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService;
@StorIOSQLiteType(table = MangaSyncTable.TABLE)
public class MangaSync implements Serializable {

View File

@ -0,0 +1,56 @@
package eu.kanade.tachiyomi.data.database.resolvers;
import android.database.Cursor;
import android.support.annotation.NonNull;
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver;
import java.util.Date;
import eu.kanade.tachiyomi.data.database.models.Chapter;
import eu.kanade.tachiyomi.data.database.models.ChapterStorIOSQLiteGetResolver;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.database.models.MangaChapter;
import eu.kanade.tachiyomi.data.database.models.MangaStorIOSQLiteGetResolver;
import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
import eu.kanade.tachiyomi.data.database.tables.MangaTable;
public class MangaChapterGetResolver extends DefaultGetResolver<MangaChapter> {
public static final MangaChapterGetResolver INSTANCE = new MangaChapterGetResolver();
public static final String QUERY = String.format(
"SELECT * FROM %1$s JOIN %2$s on %1$s.%3$s = %2$s.%4$s",
MangaTable.TABLE,
ChapterTable.TABLE,
MangaTable.COLUMN_ID,
ChapterTable.COLUMN_MANGA_ID);
public static String getRecentChaptersQuery(Date date) {
return QUERY + String.format(" WHERE %1$s = 1 AND %2$s > %3$d ORDER BY %2$s DESC",
MangaTable.COLUMN_FAVORITE,
ChapterTable.COLUMN_DATE_UPLOAD,
date.getTime());
}
@NonNull
private final MangaStorIOSQLiteGetResolver mangaGetResolver;
@NonNull
private final ChapterStorIOSQLiteGetResolver chapterGetResolver;
public MangaChapterGetResolver() {
this.mangaGetResolver = new MangaStorIOSQLiteGetResolver();
this.chapterGetResolver = new ChapterStorIOSQLiteGetResolver();
}
@NonNull
@Override
public MangaChapter mapFromCursor(@NonNull Cursor cursor) {
final Manga manga = mangaGetResolver.mapFromCursor(cursor);
final Chapter chapter = chapterGetResolver.mapFromCursor(cursor);
manga.id = chapter.manga_id;
return new MangaChapter(manga, chapter);
}
}

View File

@ -1,23 +0,0 @@
package eu.kanade.tachiyomi.data.database.resolvers;
import android.database.Cursor;
import android.support.annotation.NonNull;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.database.models.MangaStorIOSQLiteGetResolver;
import eu.kanade.tachiyomi.data.database.tables.MangaTable;
public class MangaWithUnreadGetResolver extends MangaStorIOSQLiteGetResolver {
public static final MangaWithUnreadGetResolver INSTANCE = new MangaWithUnreadGetResolver();
@Override
@NonNull
public Manga mapFromCursor(@NonNull Cursor cursor) {
Manga manga = super.mapFromCursor(cursor);
int unreadColumn = cursor.getColumnIndex(MangaTable.COLUMN_UNREAD);
manga.unread = cursor.getInt(unreadColumn);
return manga;
}
}

View File

@ -27,6 +27,7 @@ import eu.kanade.tachiyomi.data.source.model.Page;
import eu.kanade.tachiyomi.event.DownloadChaptersEvent;
import eu.kanade.tachiyomi.util.DiskUtils;
import eu.kanade.tachiyomi.util.DynamicConcurrentMergeOperator;
import eu.kanade.tachiyomi.util.UrlUtil;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
@ -42,10 +43,13 @@ public class DownloadManager {
private PreferencesHelper preferences;
private Gson gson;
private PublishSubject<Download> downloadsQueueSubject;
private PublishSubject<List<Download>> downloadsQueueSubject;
private BehaviorSubject<Boolean> runningSubject;
private Subscription downloadsSubscription;
private BehaviorSubject<Integer> threadsSubject;
private Subscription threadsSubscription;
private DownloadQueue queue;
private volatile boolean isRunning;
@ -61,14 +65,19 @@ public class DownloadManager {
downloadsQueueSubject = PublishSubject.create();
runningSubject = BehaviorSubject.create();
threadsSubject = BehaviorSubject.create();
}
private void initializeSubscriptions() {
if (downloadsSubscription != null && !downloadsSubscription.isUnsubscribed())
downloadsSubscription.unsubscribe();
threadsSubscription = preferences.downloadThreads().asObservable()
.subscribe(threadsSubject::onNext);
downloadsSubscription = downloadsQueueSubject
.flatMap(this::downloadChapter, preferences.downloadThreads())
.flatMap(Observable::from)
.lift(new DynamicConcurrentMergeOperator<>(this::downloadChapter, threadsSubject))
.onBackpressureBuffer()
.observeOn(AndroidSchedulers.mainThread())
.map(download -> areAllDownloadsFinished())
@ -94,6 +103,11 @@ public class DownloadManager {
downloadsSubscription.unsubscribe();
downloadsSubscription = null;
}
if (threadsSubscription != null && !threadsSubscription.isUnsubscribed()) {
threadsSubscription.unsubscribe();
}
}
// Create a download object for every chapter in the event and add them to the downloads queue
@ -103,6 +117,7 @@ public class DownloadManager {
// Used to avoid downloading chapters with the same name
final List<String> addedChapters = new ArrayList<>();
final List<Download> pending = new ArrayList<>();
for (Chapter chapter : event.getChapters()) {
if (addedChapters.contains(chapter.name))
@ -113,9 +128,10 @@ public class DownloadManager {
if (!prepareDownload(download)) {
queue.add(download);
if (isRunning) downloadsQueueSubject.onNext(download);
pending.add(download);
}
}
if (isRunning) downloadsQueueSubject.onNext(pending);
}
// Public method to check if a chapter is downloaded
@ -179,8 +195,7 @@ public class DownloadManager {
// Or if the page list already exists, start from the file
Observable.just(download.pages);
return pageListObservable
.subscribeOn(Schedulers.io())
return Observable.defer(() -> pageListObservable
.doOnNext(pages -> {
download.downloadedImages = 0;
download.setStatus(Download.DOWNLOADING);
@ -197,7 +212,8 @@ public class DownloadManager {
.onErrorResumeNext(error -> {
download.setStatus(Download.ERROR);
return Observable.just(download);
});
}))
.subscribeOn(Schedulers.io());
}
// Get the image from the filesystem if it exists or download from network
@ -269,7 +285,16 @@ public class DownloadManager {
// Get the filename for an image given the page
private String getImageFilename(Page page) {
String url = page.getImageUrl();
return Uri.parse(url).getLastPathSegment();
int number = page.getPageNumber() + 1;
// Try to preserve file extension
if (UrlUtil.isJpg(url)) {
return number + ".jpg";
} else if (UrlUtil.isPng(url)) {
return number + ".png";
} else if (UrlUtil.isGif(url)) {
return number + ".gif";
}
return Uri.parse(url).getLastPathSegment().replaceAll("[^\\sa-zA-Z0-9.-]", "_");
}
private boolean isImageDownloaded(File imagePath) {
@ -387,18 +412,19 @@ public class DownloadManager {
if (queue.isEmpty())
return false;
boolean hasPendingDownloads = false;
if (downloadsSubscription == null)
initializeSubscriptions();
final List<Download> pending = new ArrayList<>();
for (Download download : queue) {
if (download.getStatus() != Download.DOWNLOADED) {
if (download.getStatus() != Download.QUEUE) download.setStatus(Download.QUEUE);
if (!hasPendingDownloads) hasPendingDownloads = true;
downloadsQueueSubject.onNext(download);
pending.add(download);
}
}
return hasPendingDownloads;
downloadsQueueSubject.onNext(pending);
return !pending.isEmpty();
}
public void stopDownloads() {

View File

@ -116,6 +116,10 @@ public class PreferencesHelper {
return rxPrefs.getInteger(getKey(R.string.pref_reader_theme_key), 0);
}
public Preference<Boolean> catalogueAsList() {
return rxPrefs.getBoolean(getKey(R.string.pref_display_catalogue_as_list), false);
}
public String getSourceUsername(Source source) {
return prefs.getString(SOURCE_ACCOUNT_USERNAME + source.getId(), "");
}
@ -155,8 +159,8 @@ public class PreferencesHelper {
prefs.edit().putString(getKey(R.string.pref_download_directory_key), path).apply();
}
public int downloadThreads() {
return prefs.getInt(getKey(R.string.pref_download_slots_key), 1);
public Preference<Integer> downloadThreads() {
return rxPrefs.getInteger(getKey(R.string.pref_download_slots_key), 1);
}
public boolean downloadOnlyOverWifi() {

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.source;
import android.content.Context;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -59,7 +60,9 @@ public class SourceManager {
}
public List<Source> getSources() {
return new ArrayList<>(sourcesMap.values());
List<Source> sources = new ArrayList<>(sourcesMap.values());
Collections.sort(sources, (s1, s2) -> s1.getName().compareTo(s2.getName()));
return sources;
}
}

View File

@ -228,12 +228,8 @@ public class Mangafox extends Source {
Elements pageUrlElements = parsedDocument.select("select.m").first().select("option:not([value=0])");
String baseUrl = parsedDocument.select("div#series a").first().attr("href").replace("1.html", "");
int counter = 1;
for (Element pageUrlElement : pageUrlElements) {
if(counter < pageUrlElements.size()) {
pageUrlList.add(baseUrl + pageUrlElement.attr("value") + ".html");
}
counter++;
pageUrlList.add(baseUrl + pageUrlElement.attr("value") + ".html");
}
return pageUrlList;

View File

@ -63,7 +63,7 @@ public class UpdateMangaSyncService extends Service {
subscriptions.add(Observable.defer(() -> sync.update(mangaSync))
.flatMap(response -> {
if (response.isSuccessful()) {
return db.insertMangaSync(mangaSync).createObservable();
return db.insertMangaSync(mangaSync).asRxObservable();
}
return Observable.error(new Exception("Could not update MAL"));
})

View File

@ -0,0 +1,12 @@
package eu.kanade.tachiyomi.event;
import eu.kanade.tachiyomi.data.database.models.Manga;
public class MangaEvent {
public final Manga manga;
public MangaEvent(Manga manga) {
this.manga = manga;
}
}

View File

@ -14,8 +14,8 @@ import eu.kanade.tachiyomi.injection.module.AppModule;
import eu.kanade.tachiyomi.injection.module.DataModule;
import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter;
import eu.kanade.tachiyomi.ui.download.DownloadPresenter;
import eu.kanade.tachiyomi.ui.library.category.CategoryPresenter;
import eu.kanade.tachiyomi.ui.library.LibraryPresenter;
import eu.kanade.tachiyomi.ui.library.category.CategoryPresenter;
import eu.kanade.tachiyomi.ui.manga.MangaActivity;
import eu.kanade.tachiyomi.ui.manga.MangaPresenter;
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter;
@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.ui.manga.info.MangaInfoPresenter;
import eu.kanade.tachiyomi.ui.manga.myanimelist.MyAnimeListPresenter;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter;
import eu.kanade.tachiyomi.ui.recent.RecentChaptersPresenter;
import eu.kanade.tachiyomi.ui.setting.SettingsAccountsFragment;
import eu.kanade.tachiyomi.ui.setting.SettingsActivity;
@ -44,6 +45,7 @@ public interface AppComponent {
void inject(DownloadPresenter downloadPresenter);
void inject(MyAnimeListPresenter myAnimeListPresenter);
void inject(CategoryPresenter categoryPresenter);
void inject(RecentChaptersPresenter recentChaptersPresenter);
void inject(ReaderActivity readerActivity);
void inject(MangaActivity mangaActivity);

View File

@ -8,9 +8,9 @@ import dagger.Module;
import dagger.Provides;
import eu.kanade.tachiyomi.data.cache.ChapterCache;
import eu.kanade.tachiyomi.data.cache.CoverCache;
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager;
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
import eu.kanade.tachiyomi.data.download.DownloadManager;
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager;
import eu.kanade.tachiyomi.data.network.NetworkHelper;
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
import eu.kanade.tachiyomi.data.source.SourceManager;

View File

@ -107,14 +107,14 @@ public class RxPresenter<View> extends Presenter<View> {
}
/**
* Checks if a restartable is subscribed.
* Checks if a restartable is unsubscribed.
*
* @param restartableId id of a restartable.
* @return True if the restartable is subscribed, false otherwise.
* @param restartableId id of the restartable.
* @return true if the subscription is null or unsubscribed, false otherwise.
*/
public boolean isSubscribed(int restartableId) {
Subscription s = restartableSubscriptions.get(restartableId);
return s != null && !s.isUnsubscribed();
public boolean isUnsubscribed(int restartableId) {
Subscription subscription = restartableSubscriptions.get(restartableId);
return subscription == null || subscription.isUnsubscribed();
}
/**

View File

@ -31,6 +31,10 @@ public class CatalogueAdapter extends FlexibleAdapter<CatalogueHolder, Manga> {
notifyDataSetChanged();
}
public List<Manga> getItems() {
return mItems;
}
@Override
public long getItemId(int position) {
return mItems.get(position).id;
@ -44,8 +48,13 @@ public class CatalogueAdapter extends FlexibleAdapter<CatalogueHolder, Manga> {
@Override
public CatalogueHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = fragment.getActivity().getLayoutInflater();
View v = inflater.inflate(R.layout.item_catalogue, parent, false);
return new CatalogueHolder(v, this, fragment);
if (parent.getId() == R.id.catalogue_grid) {
View v = inflater.inflate(R.layout.item_catalogue_grid, parent, false);
return new CatalogueGridHolder(v, this, fragment);
} else {
View v = inflater.inflate(R.layout.item_catalogue_list, parent, false);
return new CatalogueListHolder(v, this, fragment);
}
}
@Override

View File

@ -4,7 +4,10 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
@ -14,9 +17,14 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.ViewSwitcher;
import com.afollestad.materialdialogs.MaterialDialog;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -28,11 +36,13 @@ import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.source.base.Source;
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
import eu.kanade.tachiyomi.ui.decoration.DividerItemDecoration;
import eu.kanade.tachiyomi.ui.main.MainActivity;
import eu.kanade.tachiyomi.ui.manga.MangaActivity;
import eu.kanade.tachiyomi.util.ToastUtil;
import eu.kanade.tachiyomi.widget.AutofitRecyclerView;
import eu.kanade.tachiyomi.widget.EndlessRecyclerScrollListener;
import eu.kanade.tachiyomi.widget.EndlessGridScrollListener;
import eu.kanade.tachiyomi.widget.EndlessListScrollListener;
import icepick.State;
import nucleus.factory.RequiresPresenter;
import rx.Subscription;
@ -43,14 +53,17 @@ import rx.subjects.PublishSubject;
public class CatalogueFragment extends BaseRxFragment<CataloguePresenter>
implements FlexibleViewHolder.OnListItemClickListener {
@Bind(R.id.recycler) AutofitRecyclerView recycler;
@Bind(R.id.switcher) ViewSwitcher switcher;
@Bind(R.id.catalogue_grid) AutofitRecyclerView catalogueGrid;
@Bind(R.id.catalogue_list) RecyclerView catalogueList;
@Bind(R.id.progress) ProgressBar progress;
@Bind(R.id.progress_grid) ProgressBar progressGrid;
private Toolbar toolbar;
private Spinner spinner;
private CatalogueAdapter adapter;
private EndlessRecyclerScrollListener scrollListener;
private EndlessGridScrollListener gridScrollListener;
private EndlessListScrollListener listScrollListener;
@State String query = "";
@State int selectedIndex = -1;
@ -59,6 +72,9 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter>
private PublishSubject<String> queryDebouncerSubject;
private Subscription queryDebouncerSubscription;
private MenuItem displayMode;
private MenuItem searchItem;
public static CatalogueFragment newInstance() {
return new CatalogueFragment();
}
@ -75,13 +91,32 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter>
View view = inflater.inflate(R.layout.fragment_catalogue, container, false);
ButterKnife.bind(this, view);
// Initialize adapter and scroll listener
GridLayoutManager layoutManager = (GridLayoutManager) recycler.getLayoutManager();
// Initialize adapter, scroll listener and recycler views
adapter = new CatalogueAdapter(this);
scrollListener = new EndlessRecyclerScrollListener(layoutManager, this::requestNextPage);
recycler.setHasFixedSize(true);
recycler.setAdapter(adapter);
recycler.addOnScrollListener(scrollListener);
GridLayoutManager glm = (GridLayoutManager) catalogueGrid.getLayoutManager();
gridScrollListener = new EndlessGridScrollListener(glm, this::requestNextPage);
catalogueGrid.setHasFixedSize(true);
catalogueGrid.setAdapter(adapter);
catalogueGrid.addOnScrollListener(gridScrollListener);
LinearLayoutManager llm = new LinearLayoutManager(getActivity());
listScrollListener = new EndlessListScrollListener(llm, this::requestNextPage);
catalogueList.setHasFixedSize(true);
catalogueList.setAdapter(adapter);
catalogueList.setLayoutManager(llm);
catalogueList.addOnScrollListener(listScrollListener);
catalogueList.addItemDecoration(new DividerItemDecoration(
ContextCompat.getDrawable(getContext(), R.drawable.line_divider)));
if (getPresenter().isListMode()) {
switcher.showNext();
}
Animation inAnim = AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_in);
Animation outAnim = AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_out);
switcher.setInAnimation(inAnim);
switcher.setOutAnimation(outAnim);
// Create toolbar spinner
Context themedContext = getBaseActivity().getSupportActionBar() != null ?
@ -107,6 +142,8 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter>
} else {
selectedIndex = position;
showProgressBar();
glm.scrollToPositionWithOffset(0, 0);
llm.scrollToPositionWithOffset(0, 0);
getPresenter().startRequesting(source);
}
}
@ -128,7 +165,7 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter>
inflater.inflate(R.menu.catalogue_list, menu);
// Initialize search menu
MenuItem searchItem = menu.findItem(R.id.action_search);
searchItem = menu.findItem(R.id.action_search);
final SearchView searchView = (SearchView) searchItem.getActionView();
if (!TextUtils.isEmpty(query)) {
@ -149,6 +186,22 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter>
return true;
}
});
// Show next display mode
displayMode = menu.findItem(R.id.action_display_mode);
int icon = getPresenter().isListMode() ?
R.drawable.ic_view_module_white_24dp : R.drawable.ic_view_list_white_24dp;
displayMode.setIcon(icon);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_display_mode:
swapDisplayMode();
break;
}
return super.onOptionsItemSelected(item);
}
@Override
@ -165,6 +218,9 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter>
@Override
public void onDestroyView() {
if (searchItem != null && searchItem.isActionViewExpanded()) {
searchItem.collapseActionView();
}
toolbar.removeView(spinner);
super.onDestroyView();
}
@ -191,11 +247,13 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter>
private void restartRequest(String newQuery) {
// If text didn't change, do nothing
if (query.equals(newQuery)) return;
if (query.equals(newQuery) || getPresenter().getSource() == null)
return;
query = newQuery;
showProgressBar();
recycler.getLayoutManager().scrollToPosition(0);
catalogueGrid.getLayoutManager().scrollToPosition(0);
catalogueList.getLayoutManager().scrollToPosition(0);
getPresenter().restartRequest(query);
}
@ -209,9 +267,10 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter>
public void onAddPage(int page, List<Manga> mangas) {
hideProgressBar();
if (page == 1) {
if (page == 0) {
adapter.clear();
scrollListener.resetScroll();
gridScrollListener.resetScroll();
listScrollListener.resetScroll();
}
adapter.addItems(mangas);
}
@ -221,15 +280,28 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter>
}
public void updateImage(Manga manga) {
CatalogueHolder holder = getHolder(manga);
CatalogueGridHolder holder = getHolder(manga);
if (holder != null) {
holder.setImage(manga, getPresenter());
}
}
public void swapDisplayMode() {
getPresenter().swapDisplayMode();
boolean isListMode = getPresenter().isListMode();
int icon = isListMode ?
R.drawable.ic_view_module_white_24dp : R.drawable.ic_view_list_white_24dp;
displayMode.setIcon(icon);
switcher.showNext();
if (!isListMode) {
// Initialize mangas if going to grid view
getPresenter().initializeMangas(adapter.getItems());
}
}
@Nullable
private CatalogueHolder getHolder(Manga manga) {
return (CatalogueHolder) recycler.findViewHolderForItemId(manga.id);
private CatalogueGridHolder getHolder(Manga manga) {
return (CatalogueGridHolder) catalogueGrid.findViewHolderForItemId(manga.id);
}
private void showProgressBar() {
@ -257,6 +329,20 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter>
@Override
public void onListItemLongClick(int position) {
// Do nothing
final Manga selectedManga = adapter.getItem(position);
int textRes = selectedManga.favorite ? R.string.remove_from_library : R.string.add_to_library;
new MaterialDialog.Builder(getActivity())
.items(getString(textRes))
.itemsCallback((dialog, itemView, which, text) -> {
switch (which) {
case 0:
getPresenter().changeMangaFavorite(selectedManga);
adapter.notifyItemChanged(position);
break;
}
})
.show();
}
}

View File

@ -0,0 +1,38 @@
package eu.kanade.tachiyomi.ui.catalogue;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import butterknife.Bind;
import butterknife.ButterKnife;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.Manga;
public class CatalogueGridHolder extends CatalogueHolder {
@Bind(R.id.title) TextView title;
@Bind(R.id.thumbnail) ImageView thumbnail;
@Bind(R.id.favorite_sticker) ImageView favoriteSticker;
public CatalogueGridHolder(View view, CatalogueAdapter adapter, OnListItemClickListener listener) {
super(view, adapter, listener);
ButterKnife.bind(this, view);
}
@Override
public void onSetValues(Manga manga, CataloguePresenter presenter) {
title.setText(manga.title);
favoriteSticker.setVisibility(manga.favorite ? View.VISIBLE : View.GONE);
setImage(manga, presenter);
}
public void setImage(Manga manga, CataloguePresenter presenter) {
if (manga.thumbnail_url != null) {
presenter.coverCache.loadFromNetwork(thumbnail, manga.thumbnail_url,
presenter.getSource().getGlideHeaders());
} else {
thumbnail.setImageResource(android.R.color.transparent);
}
}
}

View File

@ -1,38 +1,15 @@
package eu.kanade.tachiyomi.ui.catalogue;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import butterknife.Bind;
import butterknife.ButterKnife;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
public class CatalogueHolder extends FlexibleViewHolder {
@Bind(R.id.title) TextView title;
@Bind(R.id.thumbnail) ImageView thumbnail;
@Bind(R.id.favorite_sticker) ImageView favoriteSticker;
public abstract class CatalogueHolder extends FlexibleViewHolder {
public CatalogueHolder(View view, CatalogueAdapter adapter, OnListItemClickListener listener) {
super(view, adapter, listener);
ButterKnife.bind(this, view);
}
public void onSetValues(Manga manga, CataloguePresenter presenter) {
title.setText(manga.title);
favoriteSticker.setVisibility(manga.favorite ? View.VISIBLE : View.GONE);
setImage(manga, presenter);
}
public void setImage(Manga manga, CataloguePresenter presenter) {
if (manga.thumbnail_url != null) {
presenter.coverCache.loadFromNetwork(thumbnail, manga.thumbnail_url,
presenter.getSource().getGlideHeaders());
} else {
thumbnail.setImageResource(android.R.color.transparent);
}
}
}
abstract void onSetValues(Manga manga, CataloguePresenter presenter);
}

View File

@ -0,0 +1,32 @@
package eu.kanade.tachiyomi.ui.catalogue;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.TextView;
import butterknife.Bind;
import butterknife.ButterKnife;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.Manga;
public class CatalogueListHolder extends CatalogueHolder {
@Bind(R.id.title) TextView title;
private final int favoriteColor;
private final int unfavoriteColor;
public CatalogueListHolder(View view, CatalogueAdapter adapter, OnListItemClickListener listener) {
super(view, adapter, listener);
ButterKnife.bind(this, view);
favoriteColor = ContextCompat.getColor(view.getContext(), R.color.hint_text);
unfavoriteColor = ContextCompat.getColor(view.getContext(), R.color.primary_text);
}
@Override
public void onSetValues(Manga manga, CataloguePresenter presenter) {
title.setText(manga.title);
title.setTextColor(manga.favorite ? favoriteColor : unfavoriteColor);
}
}

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.catalogue;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Pair;
import com.pushtorefresh.storio.sqlite.operations.put.PutResult;
@ -38,14 +37,16 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> {
private String query;
private int currentPage;
private RxPager pager;
private RxPager<Manga> pager;
private MangasPage lastMangasPage;
private PublishSubject<List<Manga>> mangaDetailSubject;
private boolean isListMode;
private static final int GET_MANGA_LIST = 1;
private static final int GET_MANGA_DETAIL = 2;
private static final int GET_MANGA_PAGE = 3;
@Override
protected void onCreate(Bundle savedState) {
@ -57,31 +58,46 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> {
mangaDetailSubject = PublishSubject.create();
pager = new RxPager<>();
restartableReplay(GET_MANGA_LIST,
() -> pager.pages().concatMap(page -> getMangasPageObservable(page + 1)),
(view, pair) -> view.onAddPage(pair.first, pair.second),
(view, error) -> {
view.onAddPageError();
Timber.e(error.getMessage());
});
pager::results,
(view, pair) -> view.onAddPage(pair.first, pair.second));
restartableFirst(GET_MANGA_PAGE,
() -> pager.request(page -> getMangasPageObservable(page + 1)),
(view, next) -> {},
(view, error) -> view.onAddPageError());
restartableLatestCache(GET_MANGA_DETAIL,
() -> mangaDetailSubject
.observeOn(Schedulers.io())
.flatMap(Observable::from)
.filter(manga -> !manga.initialized)
.window(3)
.concatMap(pack -> pack.concatMap(this::getMangaDetails))
.concatMap(this::getMangaDetails)
.onBackpressureBuffer()
.observeOn(AndroidSchedulers.mainThread()),
CatalogueFragment::updateImage,
(view, error) -> Timber.e(error.getMessage()));
add(prefs.catalogueAsList().asObservable()
.subscribe(this::setDisplayMode));
}
private void onProcessRestart() {
source = sourceManager.get(sourceId);
stop(GET_MANGA_LIST);
stop(GET_MANGA_DETAIL);
stop(GET_MANGA_PAGE);
}
private void setDisplayMode(boolean asList) {
this.isListMode = asList;
if (asList) {
stop(GET_MANGA_DETAIL);
} else {
start(GET_MANGA_DETAIL);
}
}
public void startRequesting(Source source) {
@ -92,20 +108,23 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> {
public void restartRequest(String query) {
this.query = query;
stop(GET_MANGA_LIST);
currentPage = 1;
pager = new RxPager();
stop(GET_MANGA_PAGE);
lastMangasPage = null;
start(GET_MANGA_DETAIL);
if (!isListMode) {
start(GET_MANGA_DETAIL);
}
start(GET_MANGA_LIST);
start(GET_MANGA_PAGE);
}
public void requestNext() {
if (hasNextPage())
pager.requestNext(++currentPage);
if (hasNextPage()) {
start(GET_MANGA_PAGE);
}
}
private Observable<Pair<Integer, List<Manga>>> getMangasPageObservable(int page) {
private Observable<List<Manga>> getMangasPageObservable(int page) {
MangasPage nextMangasPage = new MangasPage(page);
if (page != 1) {
nextMangasPage.url = lastMangasPage.nextPageUrl;
@ -120,11 +139,7 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> {
.flatMap(mangasPage -> Observable.from(mangasPage.mangas))
.map(this::networkToLocalManga)
.toList()
.map(mangas -> Pair.create(page, mangas))
.doOnNext(pair -> {
if (mangaDetailSubject != null)
mangaDetailSubject.onNext(pair.second);
})
.doOnNext(this::initializeMangas)
.observeOn(AndroidSchedulers.mainThread());
}
@ -138,9 +153,12 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> {
return localManga;
}
public void initializeMangas(List<Manga> mangas) {
mangaDetailSubject.onNext(mangas);
}
private Observable<Manga> getMangaDetails(final Manga manga) {
return source.pullMangaFromNetwork(manga.url)
.subscribeOn(Schedulers.io())
.flatMap(networkManga -> {
manga.copyFrom(networkManga);
db.insertManga(manga).executeAsBlocking();
@ -170,4 +188,17 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> {
return sourceManager.getSources();
}
public void changeMangaFavorite(Manga manga) {
manga.favorite = !manga.favorite;
db.insertManga(manga).executeAsBlocking();
}
public boolean isListMode() {
return isListMode;
}
public void swapDisplayMode() {
prefs.catalogueAsList().set(!isListMode);
}
}

View File

@ -2,13 +2,13 @@ package eu.kanade.tachiyomi.ui.decoration;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.LinearLayoutManager;
import android.view.View;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.Canvas;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
public class DividerItemDecoration extends RecyclerView.ItemDecoration {

View File

@ -90,6 +90,7 @@ public class DownloadPresenter extends BasePresenter<DownloadFragment> {
.flatMap(tick -> Observable.from(download.pages)
.map(Page::getProgress)
.reduce((x, y) -> x + y))
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(progress -> {
if (download.totalProgress != progress) {

View File

@ -32,8 +32,10 @@ public class LibraryAdapter extends SmartFragmentStatePagerAdapter {
}
public void setCategories(List<Category> categories) {
this.categories = categories;
notifyDataSetChanged();
if (this.categories != categories) {
this.categories = categories;
notifyDataSetChanged();
}
}
public void setSelectionMode(int mode) {

View File

@ -52,7 +52,7 @@ public class LibraryCategoryAdapter extends FlexibleAdapter<LibraryHolder, Manga
@Override
public LibraryHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(fragment.getActivity()).inflate(R.layout.item_catalogue, parent, false);
View v = LayoutInflater.from(fragment.getActivity()).inflate(R.layout.item_catalogue_grid, parent, false);
return new LibraryHolder(v, this, fragment);
}
@ -67,7 +67,7 @@ public class LibraryCategoryAdapter extends FlexibleAdapter<LibraryHolder, Manga
}
public int getCoverHeight() {
return fragment.recycler.getItemWidth() / 9 * 12;
return fragment.recycler.getItemWidth() / 3 * 4;
}
@Override

View File

@ -34,6 +34,7 @@ public class LibraryCategoryFragment extends BaseFragment
@State int position;
private LibraryCategoryAdapter adapter;
private List<Manga> mangas;
private Subscription numColumnsSubscription;
@ -112,10 +113,13 @@ public class LibraryCategoryFragment extends BaseFragment
Category category = categories.get(position);
List<Manga> mangas = event.getMangasForCategory(category);
if (mangas == null) {
mangas = new ArrayList<>();
if (this.mangas != mangas) {
this.mangas = mangas;
if (mangas == null) {
mangas = new ArrayList<>();
}
setMangas(mangas);
}
setMangas(mangas);
}
protected void openManga(Manga manga) {

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.ui.library;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
@ -17,6 +18,7 @@ import static android.widget.RelativeLayout.LayoutParams;
public class LibraryHolder extends FlexibleViewHolder {
@Bind(R.id.image_container) FrameLayout container;
@Bind(R.id.thumbnail) ImageView thumbnail;
@Bind(R.id.title) TextView title;
@Bind(R.id.unreadText) TextView unreadText;
@ -24,7 +26,7 @@ public class LibraryHolder extends FlexibleViewHolder {
public LibraryHolder(View view, LibraryCategoryAdapter adapter, OnListItemClickListener listener) {
super(view, adapter, listener);
ButterKnife.bind(this, view);
thumbnail.setLayoutParams(new LayoutParams(MATCH_PARENT, adapter.getCoverHeight()));
container.setLayoutParams(new LayoutParams(MATCH_PARENT, adapter.getCoverHeight()));
}
public void onSetValues(Manga manga, LibraryPresenter presenter) {

View File

@ -58,7 +58,7 @@ public class LibraryPresenter extends BasePresenter<LibraryFragment> {
@Override
protected void onTakeView(LibraryFragment libraryFragment) {
super.onTakeView(libraryFragment);
if (!isSubscribed(GET_LIBRARY)) {
if (isUnsubscribed(GET_LIBRARY)) {
start(GET_LIBRARY);
}
}
@ -70,12 +70,12 @@ public class LibraryPresenter extends BasePresenter<LibraryFragment> {
}
private Observable<List<Category>> getCategoriesObservable() {
return db.getCategories().createObservable()
return db.getCategories().asRxObservable()
.doOnNext(categories -> this.categories = categories);
}
private Observable<Map<Integer, List<Manga>>> getLibraryMangasObservable() {
return db.getLibraryMangas().createObservable()
return db.getLibraryMangas().asRxObservable()
.flatMap(mangas -> Observable.from(mangas)
.groupBy(manga -> manga.category)
.flatMap(group -> group.toList()

View File

@ -24,7 +24,7 @@ public class CategoryPresenter extends BasePresenter<CategoryActivity> {
super.onCreate(savedState);
restartableLatestCache(GET_CATEGORIES,
() -> db.getCategories().createObservable()
() -> db.getCategories().asRxObservable()
.doOnNext(categories -> this.categories = categories)
.observeOn(AndroidSchedulers.mainThread()),
CategoryActivity::setCategories);
@ -46,11 +46,11 @@ public class CategoryPresenter extends BasePresenter<CategoryActivity> {
}
cat.order = max;
db.insertCategory(cat).createObservable().subscribe();
db.insertCategory(cat).asRxObservable().subscribe();
}
public void deleteCategories(List<Category> categories) {
db.deleteCategories(categories).createObservable().subscribe();
db.deleteCategories(categories).asRxObservable().subscribe();
}
public void reorderCategories(List<Category> categories) {
@ -58,11 +58,11 @@ public class CategoryPresenter extends BasePresenter<CategoryActivity> {
categories.get(i).order = i;
}
db.insertCategories(categories).createObservable().subscribe();
db.insertCategories(categories).asRxObservable().subscribe();
}
public void renameCategory(Category category, String name) {
category.name = name;
db.insertCategory(category).createObservable().subscribe();
db.insertCategory(category).asRxObservable().subscribe();
}
}

View File

@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.ui.base.activity.BaseActivity;
import eu.kanade.tachiyomi.ui.catalogue.CatalogueFragment;
import eu.kanade.tachiyomi.ui.download.DownloadFragment;
import eu.kanade.tachiyomi.ui.library.LibraryFragment;
import eu.kanade.tachiyomi.ui.recent.RecentChaptersFragment;
import eu.kanade.tachiyomi.ui.setting.SettingsActivity;
import icepick.State;
import nucleus.view.ViewWithPresenter;
@ -71,9 +72,9 @@ public class MainActivity extends BaseActivity {
new PrimaryDrawerItem()
.withName(R.string.label_library)
.withIdentifier(R.id.nav_drawer_library),
// new PrimaryDrawerItem()
// .withName(R.string.recent_updates_title)
// .withIdentifier(R.id.nav_drawer_recent_updates),
new PrimaryDrawerItem()
.withName(R.string.label_recent_updates)
.withIdentifier(R.id.nav_drawer_recent_updates),
new PrimaryDrawerItem()
.withName(R.string.label_catalogues)
.withIdentifier(R.id.nav_drawer_catalogues),
@ -95,6 +96,7 @@ public class MainActivity extends BaseActivity {
setFragment(LibraryFragment.newInstance());
break;
case R.id.nav_drawer_recent_updates:
setFragment(RecentChaptersFragment.newInstance());
break;
case R.id.nav_drawer_catalogues:
setFragment(CatalogueFragment.newInstance());

View File

@ -14,6 +14,7 @@ import javax.inject.Inject;
import butterknife.Bind;
import butterknife.ButterKnife;
import de.greenrobot.event.EventBus;
import eu.kanade.tachiyomi.App;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.Manga;
@ -30,21 +31,21 @@ public class MangaActivity extends BaseRxActivity<MangaPresenter> {
@Bind(R.id.toolbar) Toolbar toolbar;
@Bind(R.id.tabs) TabLayout tabs;
@Bind(R.id.view_pager) ViewPager view_pager;
@Bind(R.id.view_pager) ViewPager viewPager;
@Inject PreferencesHelper preferences;
@Inject MangaSyncManager mangaSyncManager;
private MangaDetailAdapter adapter;
private long manga_id;
private boolean is_online;
private boolean isOnline;
public final static String MANGA_ID = "manga_id";
public final static String MANGA_ONLINE = "manga_online";
public static Intent newIntent(Context context, Manga manga) {
Intent intent = new Intent(context, MangaActivity.class);
intent.putExtra(MANGA_ID, manga.id);
if (manga != null) {
EventBus.getDefault().postSticky(manga);
}
return intent;
}
@ -59,23 +60,19 @@ public class MangaActivity extends BaseRxActivity<MangaPresenter> {
Intent intent = getIntent();
manga_id = intent.getLongExtra(MANGA_ID, -1);
is_online = intent.getBooleanExtra(MANGA_ONLINE, false);
isOnline = intent.getBooleanExtra(MANGA_ONLINE, false);
setupViewPager();
if (savedState == null)
getPresenter().queryManga(manga_id);
}
private void setupViewPager() {
adapter = new MangaDetailAdapter(getSupportFragmentManager(), this);
view_pager.setAdapter(adapter);
tabs.setupWithViewPager(view_pager);
viewPager.setAdapter(adapter);
tabs.setupWithViewPager(viewPager);
if (!is_online)
view_pager.setCurrentItem(MangaDetailAdapter.CHAPTERS_FRAGMENT);
if (!isOnline)
viewPager.setCurrentItem(MangaDetailAdapter.CHAPTERS_FRAGMENT);
}
public void setManga(Manga manga) {
@ -83,7 +80,7 @@ public class MangaActivity extends BaseRxActivity<MangaPresenter> {
}
public boolean isCatalogueManga() {
return is_online;
return isOnline;
}
class MangaDetailAdapter extends FragmentPagerAdapter {
@ -104,7 +101,7 @@ public class MangaActivity extends BaseRxActivity<MangaPresenter> {
};
pageCount = 2;
if (!is_online && mangaSyncManager.getMyAnimeList().isLogged())
if (!isOnline && mangaSyncManager.getMyAnimeList().isLogged())
pageCount++;
}

View File

@ -7,44 +7,48 @@ import javax.inject.Inject;
import de.greenrobot.event.EventBus;
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.event.MangaEvent;
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
import eu.kanade.tachiyomi.util.EventBusHook;
import icepick.State;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
public class MangaPresenter extends BasePresenter<MangaActivity> {
@Inject DatabaseHelper db;
@State long mangaId;
@State Manga manga;
private static final int DB_MANGA = 1;
private static final int GET_MANGA = 1;
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
restartableLatestCache(DB_MANGA, this::getDbMangaObservable, MangaActivity::setManga);
restartableLatestCache(GET_MANGA, this::getMangaObservable, MangaActivity::setManga);
if (savedState == null)
registerForStickyEvents();
}
@Override
protected void onDestroy() {
super.onDestroy();
// Avoid new instances receiving wrong manga
EventBus.getDefault().removeStickyEvent(Manga.class);
EventBus.getDefault().removeStickyEvent(MangaEvent.class);
}
private Observable<Manga> getDbMangaObservable() {
return db.getManga(mangaId).createObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(manga -> EventBus.getDefault().postSticky(manga));
private Observable<Manga> getMangaObservable() {
return Observable.just(manga)
.doOnNext(manga -> EventBus.getDefault().postSticky(new MangaEvent(manga)));
}
public void queryManga(long mangaId) {
this.mangaId = mangaId;
start(DB_MANGA);
@EventBusHook
public void onEventMainThread(Manga manga) {
EventBus.getDefault().removeStickyEvent(manga);
unregisterForEvents();
this.manga = manga;
start(GET_MANGA);
}
}

View File

@ -24,6 +24,7 @@ import butterknife.Bind;
import butterknife.ButterKnife;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.Chapter;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.download.DownloadService;
import eu.kanade.tachiyomi.data.download.model.Download;
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
@ -71,26 +72,14 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
// Init RecyclerView and adapter
linearLayout = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(linearLayout);
recyclerView.addItemDecoration(new DividerItemDecoration(ContextCompat.getDrawable(getContext(), R.drawable.line_divider)));
recyclerView.addItemDecoration(new DividerItemDecoration(
ContextCompat.getDrawable(getContext(), R.drawable.line_divider)));
recyclerView.setHasFixedSize(true);
adapter = new ChaptersAdapter(this);
recyclerView.setAdapter(adapter);
// Set initial values
setReadFilter();
setDownloadedFilter();
setSortIcon();
// Init listeners
swipeRefresh.setOnRefreshListener(this::fetchChapters);
readCb.setOnCheckedChangeListener((arg, isChecked) ->
getPresenter().setReadFilter(isChecked));
downloadedCb.setOnCheckedChangeListener((v, isChecked) ->
getPresenter().setDownloadedFilter(isChecked));
sortBtn.setOnClickListener(v -> {
getPresenter().revertSortOrder();
setSortIcon();
});
nextUnreadBtn.setOnClickListener(v -> {
Chapter chapter = getPresenter().getNextUnreadChapter();
if (chapter != null) {
@ -103,16 +92,26 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
return view;
}
@Override
public void onResume() {
super.onResume();
observeChapterDownloadProgress();
}
public void onNextManga(Manga manga) {
// Remove listeners before setting the values
readCb.setOnCheckedChangeListener(null);
downloadedCb.setOnCheckedChangeListener(null);
sortBtn.setOnClickListener(null);
@Override
public void onPause() {
unsubscribeChapterDownloadProgress();
super.onPause();
// Set initial values
setReadFilter();
setDownloadedFilter();
setSortIcon();
// Init listeners
readCb.setOnCheckedChangeListener((arg, isChecked) ->
getPresenter().setReadFilter(isChecked));
downloadedCb.setOnCheckedChangeListener((v, isChecked) ->
getPresenter().setDownloadedFilter(isChecked));
sortBtn.setOnClickListener(v -> {
getPresenter().revertSortOrder();
setSortIcon();
});
}
public void onNextChapters(List<Chapter> chapters) {
@ -175,10 +174,10 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
holder.onProgressChange(getContext(), download.downloadedImages, download.pages.size());
}
public void onChapterStatusChange(Chapter chapter) {
ChaptersHolder holder = getHolder(chapter);
public void onChapterStatusChange(Download download) {
ChaptersHolder holder = getHolder(download.chapter);
if (holder != null)
holder.onStatusChange(chapter.status);
holder.onStatusChange(download.getStatus());
}
@Nullable
@ -254,6 +253,11 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
return true;
}
public boolean onMarkPreviousAsRead(Chapter chapter) {
getPresenter().markPreviousChaptersAsRead(chapter);
return true;
}
protected boolean onDownload(Observable<Chapter> chapters) {
DownloadService.start(getActivity());

View File

@ -31,23 +31,24 @@ public class ChaptersHolder extends FlexibleViewHolder {
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
private final int readColor;
private final int unreadColor;
public ChaptersHolder(View view, ChaptersAdapter adapter, OnListItemClickListener listener) {
super(view, adapter, listener);
this.adapter = adapter;
ButterKnife.bind(this, view);
readColor = ContextCompat.getColor(view.getContext(), R.color.hint_text);
unreadColor = ContextCompat.getColor(view.getContext(), R.color.primary_text);
chapterMenu.setOnClickListener(v -> v.post(() -> showPopupMenu(v)));
}
public void onSetValues(Context context, Chapter chapter) {
this.item = chapter;
title.setText(chapter.name);
if (chapter.read) {
title.setTextColor(ContextCompat.getColor(context, R.color.hint_text));
} else {
title.setTextColor(ContextCompat.getColor(context, R.color.primary_text));
}
title.setTextColor(chapter.read ? readColor : unreadColor);
if (!chapter.read && chapter.last_page_read > 0) {
pages.setText(context.getString(R.string.chapter_progress, chapter.last_page_read + 1));
@ -99,6 +100,8 @@ public class ChaptersHolder extends FlexibleViewHolder {
return adapter.getFragment().onDownload(chapter);
case R.id.action_delete:
return adapter.getFragment().onDelete(chapter);
case R.id.action_mark_previous_as_read:
return adapter.getFragment().onMarkPreviousAsRead(item);
}
return false;
});

View File

@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.data.source.SourceManager;
import eu.kanade.tachiyomi.data.source.base.Source;
import eu.kanade.tachiyomi.event.ChapterCountEvent;
import eu.kanade.tachiyomi.event.DownloadChaptersEvent;
import eu.kanade.tachiyomi.event.MangaEvent;
import eu.kanade.tachiyomi.event.ReaderEvent;
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
import eu.kanade.tachiyomi.util.EventBusHook;
@ -38,16 +39,16 @@ public class ChaptersPresenter extends BasePresenter<ChaptersFragment> {
private Manga manga;
private Source source;
private List<Chapter> chapters;
private boolean sortOrderAToZ = true;
private boolean onlyUnread = true;
private boolean onlyDownloaded;
@State boolean hasRequested;
private PublishSubject<List<Chapter>> chaptersSubject;
private static final int DB_CHAPTERS = 1;
private static final int FETCH_CHAPTERS = 2;
private static final int CHAPTER_STATUS_CHANGES = 3;
private static final int GET_MANGA = 1;
private static final int DB_CHAPTERS = 2;
private static final int FETCH_CHAPTERS = 3;
private static final int CHAPTER_STATUS_CHANGES = 4;
@Override
protected void onCreate(Bundle savedState) {
@ -59,6 +60,10 @@ public class ChaptersPresenter extends BasePresenter<ChaptersFragment> {
chaptersSubject = PublishSubject.create();
restartableLatestCache(GET_MANGA,
() -> Observable.just(manga),
ChaptersFragment::onNextManga);
restartableLatestCache(DB_CHAPTERS,
this::getDbChaptersObs,
ChaptersFragment::onNextChapters);
@ -70,13 +75,14 @@ public class ChaptersPresenter extends BasePresenter<ChaptersFragment> {
restartableLatestCache(CHAPTER_STATUS_CHANGES,
this::getChapterStatusObs,
(view, download) -> view.onChapterStatusChange(download.chapter),
(view, download) -> view.onChapterStatusChange(download),
(view, error) -> Timber.e(error.getCause(), error.getMessage()));
registerForStickyEvents();
}
private void onProcessRestart() {
stop(GET_MANGA);
stop(DB_CHAPTERS);
stop(FETCH_CHAPTERS);
stop(CHAPTER_STATUS_CHANGES);
@ -90,14 +96,15 @@ public class ChaptersPresenter extends BasePresenter<ChaptersFragment> {
}
@EventBusHook
public void onEventMainThread(Manga manga) {
this.manga = manga;
public void onEventMainThread(MangaEvent event) {
this.manga = event.manga;
start(GET_MANGA);
if (!isSubscribed(DB_CHAPTERS)) {
if (isUnsubscribed(DB_CHAPTERS)) {
source = sourceManager.get(manga.source);
start(DB_CHAPTERS);
add(db.getChapters(manga).createObservable()
add(db.getChapters(manga).asRxObservable()
.subscribeOn(Schedulers.io())
.doOnNext(chapters -> {
this.chapters = chapters;
@ -141,7 +148,7 @@ public class ChaptersPresenter extends BasePresenter<ChaptersFragment> {
if (onlyDownloaded) {
observable = observable.filter(chapter -> chapter.status == Download.DOWNLOADED);
}
return observable.toSortedList((chapter, chapter2) -> sortOrderAToZ ?
return observable.toSortedList((chapter, chapter2) -> getSortOrder() ?
Float.compare(chapter2.chapter_number, chapter.chapter_number) :
Float.compare(chapter.chapter_number, chapter2.chapter_number));
}
@ -202,11 +209,20 @@ public class ChaptersPresenter extends BasePresenter<ChaptersFragment> {
return chapter;
})
.toList()
.flatMap(chapters -> db.insertChapters(chapters).createObservable())
.flatMap(chapters -> db.insertChapters(chapters).asRxObservable())
.observeOn(AndroidSchedulers.mainThread())
.subscribe());
}
public void markPreviousChaptersAsRead(Chapter selected) {
Observable.from(chapters)
.filter(c -> c.chapter_number > -1 && c.chapter_number < selected.chapter_number)
.doOnNext(c -> c.read = true)
.toList()
.flatMap(chapters -> db.insertChapters(chapters).asRxObservable())
.subscribe();
}
public void downloadChapters(Observable<Chapter> selectedChapters) {
add(selectedChapters
.toList()
@ -232,8 +248,8 @@ public class ChaptersPresenter extends BasePresenter<ChaptersFragment> {
}
public void revertSortOrder() {
//TODO manga.chapter_order
sortOrderAToZ = !sortOrderAToZ;
manga.setChapterOrder(getSortOrder() ? Manga.SORT_ZA : Manga.SORT_AZ);
db.insertManga(manga).executeAsBlocking();
refreshChapters();
}
@ -249,7 +265,7 @@ public class ChaptersPresenter extends BasePresenter<ChaptersFragment> {
}
public boolean getSortOrder() {
return sortOrderAToZ;
return manga.sortChaptersAZ();
}
public boolean getReadFilter() {

View File

@ -16,6 +16,7 @@ import butterknife.ButterKnife;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.cache.CoverCache;
import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.source.base.Source;
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
import nucleus.factory.RequiresPresenter;
@ -29,6 +30,7 @@ public class MangaInfoFragment extends BaseRxFragment<MangaInfoPresenter> {
@Bind(R.id.manga_chapters) TextView chapterCount;
@Bind(R.id.manga_genres) TextView genres;
@Bind(R.id.manga_status) TextView status;
@Bind(R.id.manga_source) TextView source;
@Bind(R.id.manga_summary) TextView description;
@Bind(R.id.manga_cover) ImageView cover;
@ -60,18 +62,22 @@ public class MangaInfoFragment extends BaseRxFragment<MangaInfoPresenter> {
return view;
}
public void onNextManga(Manga manga) {
public void onNextManga(Manga manga, Source source) {
if (manga.initialized) {
setMangaInfo(manga);
setMangaInfo(manga, source);
} else {
// Initialize manga
fetchMangaFromSource();
}
}
private void setMangaInfo(Manga manga) {
private void setMangaInfo(Manga manga, Source mangaSource) {
artist.setText(manga.artist);
author.setText(manga.author);
if (mangaSource != null) {
source.setText(mangaSource.getName());
}
genres.setText(manga.genre);
status.setText(manga.getStatus(getActivity()));
description.setText(manga.description);

View File

@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.source.SourceManager;
import eu.kanade.tachiyomi.data.source.base.Source;
import eu.kanade.tachiyomi.event.ChapterCountEvent;
import eu.kanade.tachiyomi.event.MangaEvent;
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
import eu.kanade.tachiyomi.util.EventBusHook;
import rx.Observable;
@ -26,8 +27,6 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
protected Source source;
private int count = -1;
private boolean isFetching;
private static final int GET_MANGA = 1;
private static final int GET_CHAPTER_COUNT = 2;
private static final int FETCH_MANGA_INFO = 3;
@ -42,7 +41,7 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
restartableLatestCache(GET_MANGA,
() -> Observable.just(manga),
MangaInfoFragment::onNextManga);
(view, manga) -> view.onNextManga(manga, source));
restartableLatestCache(GET_CHAPTER_COUNT,
() -> Observable.just(count),
@ -69,10 +68,10 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
}
@EventBusHook
public void onEventMainThread(Manga manga) {
this.manga = manga;
public void onEventMainThread(MangaEvent event) {
this.manga = event.manga;
source = sourceManager.get(manga.source);
start(GET_MANGA);
refreshManga();
}
@EventBusHook
@ -84,8 +83,7 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
}
public void fetchMangaFromSource() {
if (!isFetching) {
isFetching = true;
if (isUnsubscribed(FETCH_MANGA_INFO)) {
start(FETCH_MANGA_INFO);
}
}
@ -97,15 +95,16 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
db.insertManga(manga).executeAsBlocking();
return Observable.just(manga);
})
.finallyDo(() -> isFetching = false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(manga -> refreshManga());
}
public void toggleFavorite() {
manga.favorite = !manga.favorite;
onMangaFavoriteChange(manga.favorite);
db.insertManga(manga).executeAsBlocking();
refreshManga();
}
private void onMangaFavoriteChange(boolean isFavorite) {
@ -116,4 +115,9 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
}
}
// Used to refresh the view
private void refreshManga() {
start(GET_MANGA);
}
}

View File

@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga;
import eu.kanade.tachiyomi.data.database.models.MangaSync;
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager;
import eu.kanade.tachiyomi.data.mangasync.services.MyAnimeList;
import eu.kanade.tachiyomi.event.MangaEvent;
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
import eu.kanade.tachiyomi.util.EventBusHook;
import eu.kanade.tachiyomi.util.ToastUtil;
@ -46,7 +47,7 @@ public class MyAnimeListPresenter extends BasePresenter<MyAnimeListFragment> {
myAnimeList = syncManager.getMyAnimeList();
restartableLatestCache(GET_MANGA_SYNC,
() -> db.getMangaSync(manga, myAnimeList).createObservable()
() -> db.getMangaSync(manga, myAnimeList).asRxObservable()
.doOnNext(mangaSync -> this.mangaSync = mangaSync)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
@ -75,7 +76,7 @@ public class MyAnimeListPresenter extends BasePresenter<MyAnimeListFragment> {
}
return Observable.error(new Exception("Could not find manga"));
})
.flatMap(myManga -> db.insertMangaSync(myManga).createObservable())
.flatMap(myManga -> db.insertMangaSync(myManga).asRxObservable())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()),
(view, result) -> view.onRefreshDone(),
@ -102,14 +103,14 @@ public class MyAnimeListPresenter extends BasePresenter<MyAnimeListFragment> {
}
@EventBusHook
public void onEventMainThread(Manga manga) {
this.manga = manga;
public void onEventMainThread(MangaEvent event) {
this.manga = event.manga;
start(GET_MANGA_SYNC);
}
private void updateRemote() {
add(myAnimeList.update(mangaSync)
.flatMap(response -> db.insertMangaSync(mangaSync).createObservable())
.flatMap(response -> db.insertMangaSync(mangaSync).asRxObservable())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(next -> {},
@ -139,7 +140,7 @@ public class MyAnimeListPresenter extends BasePresenter<MyAnimeListFragment> {
add(myAnimeList.bind(manga)
.flatMap(response -> {
if (response.isSuccessful()) {
return db.insertMangaSync(manga).createObservable();
return db.insertMangaSync(manga).asRxObservable();
}
return Observable.error(new Exception("Could not bind manga"));
})

View File

@ -164,6 +164,10 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
}
public void onChapterReady(List<Page> pages, Manga manga, Chapter chapter, int currentPage) {
if (currentPage == -1) {
currentPage = pages.size() - 1;
}
if (viewer == null) {
viewer = createViewer(manga);
getSupportFragmentManager().beginTransaction().replace(R.id.reader, viewer).commit();

View File

@ -172,8 +172,8 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
private Observable<Pair<Chapter, Chapter>> getAdjacentChaptersObservable() {
return Observable.zip(
db.getPreviousChapter(chapter).createObservable().take(1),
db.getNextChapter(chapter).createObservable().take(1),
db.getPreviousChapter(chapter).asRxObservable().take(1),
db.getNextChapter(chapter).asRxObservable().take(1),
Pair::create)
.doOnNext(pair -> {
previousChapter = pair.first;
@ -211,12 +211,16 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
}
private Observable<List<MangaSync>> getMangaSyncObservable() {
return db.getMangasSync(manga).createObservable()
return db.getMangasSync(manga).asRxObservable()
.doOnNext(mangaSync -> this.mangaSyncList = mangaSync);
}
// Loads the given chapter
private void loadChapter(Chapter chapter) {
loadChapter(chapter, 0);
}
// Loads the given chapter
private void loadChapter(Chapter chapter, int requestedPage) {
// Before loading the chapter, stop preloading (if it's working) and save current progress
stopPreloadingNextChapter();
@ -227,7 +231,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
if (!chapter.read && chapter.last_page_read != 0)
currentPage = chapter.last_page_read;
else
currentPage = 0;
currentPage = requestedPage;
// Reset next and previous chapter. They have to be fetched again
nextChapter = null;
@ -262,7 +266,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
if (isChapterFinished()) {
chapter.read = true;
}
db.insertChapter(chapter).createObservable().subscribe();
db.insertChapter(chapter).asRxObservable().subscribe();
}
// Check whether the chapter has been read
@ -312,7 +316,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
public boolean loadNextChapter() {
if (hasNextChapter()) {
onChapterLeft();
loadChapter(nextChapter);
loadChapter(nextChapter, 0);
return true;
}
return false;
@ -321,7 +325,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
public boolean loadPreviousChapter() {
if (hasPreviousChapter()) {
onChapterLeft();
loadChapter(previousChapter);
loadChapter(previousChapter, -1);
return true;
}
return false;
@ -342,7 +346,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
}
private void stopPreloadingNextChapter() {
if (isSubscribed(PRELOAD_NEXT_CHAPTER)) {
if (!isUnsubscribed(PRELOAD_NEXT_CHAPTER)) {
stop(PRELOAD_NEXT_CHAPTER);
if (nextChapterPageList != null)
source.savePageList(nextChapter.url, nextChapterPageList);

View File

@ -8,8 +8,8 @@ import android.view.MotionEvent;
import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterBoundariesOutListener;
import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterSingleTapListener;
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerGestureListener;
import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager;
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerGestureListener;
import rx.functions.Action1;
public class HorizontalPager extends ViewPager implements Pager {

View File

@ -7,8 +7,8 @@ import android.view.MotionEvent;
import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterBoundariesOutListener;
import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterSingleTapListener;
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerGestureListener;
import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager;
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerGestureListener;
import rx.functions.Action1;
public class VerticalPager extends VerticalViewPagerImpl implements Pager {

View File

@ -47,7 +47,6 @@ public class WebtoonAdapter extends RecyclerView.Adapter<WebtoonHolder> {
public void setPages(List<Page> pages) {
this.pages = pages;
notifyDataSetChanged();
}
public void clear() {

View File

@ -107,9 +107,8 @@ public class WebtoonReader extends BaseReader {
if (pages != null) {
unsubscribeStatus();
recycler.clearOnScrollListeners();
adapter.clear();
recycler.scrollTo(0, 0);
adapter.setPages(pages);
recycler.setAdapter(adapter);
setScrollListener();
observeStatus(0);
}

View File

@ -0,0 +1,108 @@
package eu.kanade.tachiyomi.ui.recent;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.Date;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.MangaChapter;
public class RecentChaptersAdapter extends FlexibleAdapter<RecyclerView.ViewHolder, Object> {
private RecentChaptersFragment fragment;
private static final int VIEW_TYPE_CHAPTER = 0;
private static final int VIEW_TYPE_SECTION = 1;
public RecentChaptersAdapter(RecentChaptersFragment fragment) {
this.fragment = fragment;
setHasStableIds(true);
}
@Override
public long getItemId(int position) {
Object item = getItem(position);
if (item instanceof MangaChapter)
return ((MangaChapter) item).chapter.id;
else
return item.hashCode();
}
public void setItems(List<Object> items) {
mItems = items;
notifyDataSetChanged();
}
@Override
public void updateDataSet(String param) {
}
@Override
public int getItemViewType(int position) {
return getItem(position) instanceof MangaChapter ? VIEW_TYPE_CHAPTER : VIEW_TYPE_SECTION;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View v;
switch (viewType) {
case VIEW_TYPE_CHAPTER:
v = inflater.inflate(R.layout.item_recent_chapter, parent, false);
return new RecentChaptersHolder(v, this, fragment);
case VIEW_TYPE_SECTION:
v = inflater.inflate(R.layout.item_recent_chapter_section, parent, false);
return new SectionViewHolder(v);
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case VIEW_TYPE_CHAPTER:
final MangaChapter chapter = (MangaChapter) getItem(position);
((RecentChaptersHolder) holder).onSetValues(chapter);
break;
case VIEW_TYPE_SECTION:
final Date date = (Date) getItem(position);
((SectionViewHolder) holder).onSetValues(date);
break;
}
//When user scrolls this bind the correct selection status
holder.itemView.setActivated(isSelected(position));
}
public RecentChaptersFragment getFragment() {
return fragment;
}
public static class SectionViewHolder extends RecyclerView.ViewHolder {
@Bind(R.id.section_text) TextView section;
private final long now = new Date().getTime();
public SectionViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
}
public void onSetValues(Date date) {
CharSequence s = DateUtils.getRelativeTimeSpanString(
date.getTime(), now, DateUtils.DAY_IN_MILLIS);
section.setText(s);
}
}
}

View File

@ -0,0 +1,76 @@
package eu.kanade.tachiyomi.ui.recent;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.List;
import butterknife.Bind;
import butterknife.ButterKnife;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.MangaChapter;
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
import eu.kanade.tachiyomi.ui.decoration.DividerItemDecoration;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
import nucleus.factory.RequiresPresenter;
@RequiresPresenter(RecentChaptersPresenter.class)
public class RecentChaptersFragment extends BaseRxFragment<RecentChaptersPresenter> implements FlexibleViewHolder.OnListItemClickListener {
@Bind(R.id.chapter_list) RecyclerView recyclerView;
private RecentChaptersAdapter adapter;
public static RecentChaptersFragment newInstance() {
return new RecentChaptersFragment();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_recent_chapters, container, false);
ButterKnife.bind(this, view);
// Init RecyclerView and adapter
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.addItemDecoration(new DividerItemDecoration(ContextCompat.getDrawable(
getContext(), R.drawable.line_divider)));
recyclerView.setHasFixedSize(true);
adapter = new RecentChaptersAdapter(this);
recyclerView.setAdapter(adapter);
setToolbarTitle(R.string.label_recent_updates);
return view;
}
public void onNextMangaChapters(List<Object> chapters) {
adapter.setItems(chapters);
}
@Override
public boolean onListItemClick(int position) {
Object item = adapter.getItem(position);
if (item instanceof MangaChapter) {
openChapter((MangaChapter) item);
}
return false;
}
@Override
public void onListItemLongClick(int position) {
}
protected void openChapter(MangaChapter chapter) {
getPresenter().onOpenChapter(chapter);
Intent intent = ReaderActivity.newIntent(getActivity());
startActivity(intent);
}
}

View File

@ -0,0 +1,42 @@
package eu.kanade.tachiyomi.ui.recent;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.TextView;
import butterknife.Bind;
import butterknife.ButterKnife;
import eu.kanade.tachiyomi.R;
import eu.kanade.tachiyomi.data.database.models.MangaChapter;
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
public class RecentChaptersHolder extends FlexibleViewHolder {
@Bind(R.id.chapter_title) TextView chapterTitle;
@Bind(R.id.manga_title) TextView mangaTitle;
private final int readColor;
private final int unreadColor;
public RecentChaptersHolder(View view, RecentChaptersAdapter adapter, OnListItemClickListener onListItemClickListener) {
super(view, adapter, onListItemClickListener);
ButterKnife.bind(this, view);
readColor = ContextCompat.getColor(view.getContext(), R.color.hint_text);
unreadColor = ContextCompat.getColor(view.getContext(), R.color.primary_text);
}
public void onSetValues(MangaChapter item) {
chapterTitle.setText(item.chapter.name);
mangaTitle.setText(item.manga.title);
if (item.chapter.read) {
chapterTitle.setTextColor(readColor);
mangaTitle.setTextColor(readColor);
} else {
chapterTitle.setTextColor(unreadColor);
mangaTitle.setTextColor(unreadColor);
}
}
}

View File

@ -0,0 +1,82 @@
package eu.kanade.tachiyomi.ui.recent;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.inject.Inject;
import de.greenrobot.event.EventBus;
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
import eu.kanade.tachiyomi.data.database.models.MangaChapter;
import eu.kanade.tachiyomi.data.source.SourceManager;
import eu.kanade.tachiyomi.data.source.base.Source;
import eu.kanade.tachiyomi.event.ReaderEvent;
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
import rx.Observable;
import rx.android.schedulers.AndroidSchedulers;
public class RecentChaptersPresenter extends BasePresenter<RecentChaptersFragment> {
@Inject DatabaseHelper db;
@Inject SourceManager sourceManager;
private static final int GET_RECENT_CHAPTERS = 1;
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
restartableLatestCache(GET_RECENT_CHAPTERS,
this::getRecentChaptersObservable,
RecentChaptersFragment::onNextMangaChapters);
if (savedState == null)
start(GET_RECENT_CHAPTERS);
}
private Observable<List<Object>> getRecentChaptersObservable() {
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
cal.add(Calendar.MONTH, -1);
return db.getRecentChapters(cal.getTime()).asRxObservable()
// group chapters by the date they were fetched on a ordered map
.flatMap(recents -> Observable.from(recents)
.toMultimap(
recent -> getMapKey(recent.chapter.date_fetch),
recent -> recent,
() -> new TreeMap<>((d1, d2) -> d2.compareTo(d1))))
// add every day and all its chapters to a single list
.map(recents -> {
List<Object> items = new ArrayList<>();
for (Map.Entry<Date, Collection<MangaChapter>> recent : recents.entrySet()) {
items.add(recent.getKey());
items.addAll(recent.getValue());
}
return items;
})
.observeOn(AndroidSchedulers.mainThread());
}
private Date getMapKey(long date) {
Calendar cal = Calendar.getInstance();
cal.setTime(new Date(date));
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTime();
}
public void onOpenChapter(MangaChapter item) {
Source source = sourceManager.get(item.manga.source);
EventBus.getDefault().postSticky(new ReaderEvent(source, item.manga, item.chapter));
}
}

View File

@ -12,8 +12,8 @@ import java.util.List;
import javax.inject.Inject;
import eu.kanade.tachiyomi.App;
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService;
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager;
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService;
import eu.kanade.tachiyomi.data.source.SourceManager;
import eu.kanade.tachiyomi.data.source.base.Source;
import eu.kanade.tachiyomi.widget.preference.MangaSyncLoginDialog;

View File

@ -1,32 +1,26 @@
package eu.kanade.tachiyomi.util;
import android.util.Pair;
import java.util.List;
import rx.Observable;
import rx.functions.Func1;
import rx.subjects.PublishSubject;
public class RxPager {
public class RxPager<T> {
private final int initialPageCount;
private final PublishSubject<Integer> requests = PublishSubject.create();
private final PublishSubject<List<T>> results = PublishSubject.create();
private int requestedCount;
public RxPager() {
this(1);
public Observable<Pair<Integer, List<T>>> results() {
requestedCount = 0;
return results.map(list -> Pair.create(requestedCount++, list));
}
public RxPager(int initialPageCount) {
this.initialPageCount = initialPageCount;
public Observable<List<T>> request(Func1<Integer, Observable<List<T>>> networkObservable) {
return networkObservable.call(requestedCount).doOnNext(results::onNext);
}
public void requestNext(int page) {
requests.onNext(page);
}
}
public Observable<Integer> pages() {
return requests
.concatMap(targetPage -> targetPage <= requestedCount ?
Observable.<Integer>empty() :
Observable.range(requestedCount, targetPage - requestedCount))
.startWith(Observable.range(0, initialPageCount))
.doOnNext(it -> requestedCount = it + 1);
}
}

View File

@ -5,6 +5,10 @@ import java.net.URISyntaxException;
public class UrlUtil {
private static final String JPG = ".jpg";
private static final String PNG = ".png";
private static final String GIF = ".gif";
public static String getPath(String s) {
try {
URI uri = new URI(s);
@ -18,4 +22,37 @@ public class UrlUtil {
return s;
}
}
public static boolean isJpg(String url) {
return containsIgnoreCase(url, JPG);
}
public static boolean isPng(String url) {
return containsIgnoreCase(url, PNG);
}
public static boolean isGif(String url) {
return containsIgnoreCase(url, GIF);
}
public static boolean containsIgnoreCase(String src, String what) {
final int length = what.length();
if (length == 0)
return true; // Empty string is contained
final char firstLo = Character.toLowerCase(what.charAt(0));
final char firstUp = Character.toUpperCase(what.charAt(0));
for (int i = src.length() - length; i >= 0; i--) {
// Quick check before calling the more expensive regionMatches() method:
final char ch = src.charAt(i);
if (ch != firstLo && ch != firstUp)
continue;
if (src.regionMatches(true, i, what, 0, length))
return true;
}
return false;
}
}

View File

@ -5,7 +5,7 @@ import android.support.v7.widget.RecyclerView;
import rx.functions.Action0;
public class EndlessRecyclerScrollListener extends RecyclerView.OnScrollListener {
public class EndlessGridScrollListener extends RecyclerView.OnScrollListener {
private int previousTotal = 0; // The total number of items in the dataset after the last load
private boolean loading = true; // True if we are still waiting for the last set of data to load.
@ -16,7 +16,7 @@ public class EndlessRecyclerScrollListener extends RecyclerView.OnScrollListener
private Action0 requestNext;
public EndlessRecyclerScrollListener(GridLayoutManager layoutManager, Action0 requestNext) {
public EndlessGridScrollListener(GridLayoutManager layoutManager, Action0 requestNext) {
this.layoutManager = layoutManager;
this.requestNext = requestNext;
}

View File

@ -0,0 +1,49 @@
package eu.kanade.tachiyomi.widget;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import rx.functions.Action0;
public class EndlessListScrollListener extends RecyclerView.OnScrollListener {
private int previousTotal = 0; // The total number of items in the dataset after the last load
private boolean loading = true; // True if we are still waiting for the last set of data to load.
private int visibleThreshold = 5; // The minimum amount of items to have below your current scroll position before loading more.
int firstVisibleItem, visibleItemCount, totalItemCount;
private LinearLayoutManager layoutManager;
private Action0 requestNext;
public EndlessListScrollListener(LinearLayoutManager layoutManager, Action0 requestNext) {
this.layoutManager = layoutManager;
this.requestNext = requestNext;
}
public void resetScroll() {
previousTotal = 0;
loading = true;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = layoutManager.getItemCount();
firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
if (loading && (totalItemCount > previousTotal)) {
loading = false;
previousTotal = totalItemCount;
}
if (!loading && (totalItemCount - visibleItemCount)
<= (firstVisibleItem + visibleThreshold)) {
// End has been reached
requestNext.call();
loading = true;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:angle="90"
android:startColor="#aa000000"
android:centerColor="#00000000"
android:endColor="#00ffffff"/>
<corners android:radius="0dp" />
</shape>

View File

@ -23,6 +23,7 @@
android:layout_gravity="bottom|right"
android:layout_margin="@dimen/fab_margin"
android:src="@drawable/ic_action_add_18dp"
app:backgroundTint="@color/colorPrimary"
app:layout_anchor="@id/categories_list"
app:layout_anchorGravity="bottom|right|end"
app:layout_behavior="eu.kanade.tachiyomi.ui.base.fab.ScrollAwareFABBehavior"/>

View File

@ -11,17 +11,28 @@
android:id="@+id/progress"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|center_horizontal"
android:visibility="gone"/>
<eu.kanade.tachiyomi.widget.AutofitRecyclerView
android:id="@+id/recycler"
style="@style/AppTheme.GridView"
<ViewSwitcher
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:columnWidth="140dp"
tools:listitem="@layout/item_catalogue" />
android:id="@+id/switcher">
<eu.kanade.tachiyomi.widget.AutofitRecyclerView
android:id="@+id/catalogue_grid"
style="@style/AppTheme.GridView"
android:columnWidth="140dp"
tools:listitem="@layout/item_catalogue_grid" />
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/catalogue_list"/>
</ViewSwitcher>
<ProgressBar
android:id="@+id/progress_grid"

View File

@ -8,6 +8,6 @@
android:id="@+id/library_mangas"
style="@style/AppTheme.GridView"
android:columnWidth="140dp"
tools:listitem="@layout/item_catalogue" />
tools:listitem="@layout/item_catalogue_grid" />
</FrameLayout>

View File

@ -154,7 +154,31 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/manga_status_label"
android:layout_toRightOf="@id/manga_chapters_label"
android:layout_toRightOf="@id/manga_status_label"
android:ellipsize="end"
android:focusable="false"
android:focusableInTouchMode="false"
android:maxLines="1"
android:singleLine="true" />
<TextView
android:id="@+id/manga_source_label"
style="@style/manga_detail_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@id/manga_status_label"
android:focusable="false"
android:focusableInTouchMode="false"
android:text="@string/source" />
<TextView
android:id="@+id/manga_source"
style="@style/manga_detail_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/manga_source_label"
android:layout_toRightOf="@id/manga_source_label"
android:ellipsize="end"
android:focusable="false"
android:focusableInTouchMode="false"
@ -167,7 +191,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@id/manga_status_label"
android:layout_below="@id/manga_source_label"
android:focusable="false"
android:focusableInTouchMode="false"
android:text="@string/genres" />

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/chapter_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="blocksDescendants"
android:background="@color/white"
tools:listitem="@layout/item_recent_chapter">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>

View File

@ -12,13 +12,27 @@
android:layout_height="wrap_content"
android:background="@drawable/card_background">
<ImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
<android.support.percent.PercentFrameLayout
android:layout_width="wrap_content"
android:layout_height="220dp"
android:background="@color/white"
tools:background="@color/md_red_100"
tools:src="@mipmap/ic_launcher"/>
android:id="@+id/image_container">
<ImageView
android:id="@+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
tools:background="@color/md_red_100"
tools:src="@mipmap/ic_launcher"/>
<View
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_heightPercent="50%"
android:layout_gravity="bottom"
android:background="@drawable/gradient_shape"/>
</android.support.percent.PercentFrameLayout>
<TextView
android:id="@+id/unreadText"
@ -38,39 +52,32 @@
android:id="@+id/favorite_sticker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignEnd="@+id/thumbnail"
android:layout_alignRight="@+id/thumbnail"
android:layout_alignTop="@+id/thumbnail"
android:layout_alignEnd="@+id/image_container"
android:layout_alignRight="@+id/image_container"
android:layout_alignTop="@+id/image_container"
android:layout_marginRight="5dp"
android:layout_marginTop="5dp"
android:src="@drawable/ic_action_favorite_blue"
android:visibility="invisible"/>
<FrameLayout
<eu.kanade.tachiyomi.widget.PTSansTextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/thumbnail"
android:background="@color/manga_cover_title_background">
<eu.kanade.tachiyomi.widget.PTSansTextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
app:typeface="ptsansNarrowBold"
android:lineSpacingExtra="-4dp"
android:ellipsize="end"
android:maxLines="2"
android:padding="8dp"
android:textColor="@color/white"
android:textSize="14sp"
android:shadowDx="0"
android:shadowDy="0"
android:shadowColor="@color/primary_text"
android:shadowRadius="4"
tools:text="Sample name"/>
</FrameLayout>
android:layout_gravity="center_vertical"
app:typeface="ptsansNarrowBold"
android:lineSpacingExtra="-4dp"
android:ellipsize="end"
android:maxLines="2"
android:padding="8dp"
android:textColor="@color/white"
android:textSize="14sp"
android:shadowDx="0"
android:shadowDy="0"
android:shadowColor="@color/primary_text"
android:shadowRadius="4"
android:layout_alignBottom="@+id/image_container"
tools:text="Sample name"/>
</RelativeLayout>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:background="@drawable/selector_chapter_light">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="?android:listPreferredItemPaddingLeft"
android:paddingRight="?android:listPreferredItemPaddingLeft"
android:id="@+id/title"/>
</FrameLayout>

View File

@ -66,7 +66,7 @@
android:layout_alignParentTop="true"
android:layout_alignWithParentIfMissing="true"
android:layout_marginRight="30dp"
android:ellipsize="end"
android:ellipsize="middle"
android:gravity="center_vertical"
android:maxLines="1"
android:textSize="17sp"

View File

@ -30,6 +30,8 @@
</LinearLayout>
<include layout="@layout/chapter_image"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -38,6 +40,4 @@
android:layout_gravity="center"
android:visibility="gone"/>
<include layout="@layout/chapter_image"/>
</FrameLayout>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:background="@drawable/selector_chapter_light">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="30dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:id="@+id/manga_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:singleLine="true"
tools:text="My manga"/>
<TextView
android:id="@+id/chapter_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:maxLines="1"
tools:text="Title"/>
</LinearLayout>
</RelativeLayout>
</RelativeLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="32dp"
android:gravity="center_vertical"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:background="@color/colorPrimary">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="@color/white"
android:textSize="16sp"
android:id="@+id/section_text"
android:layout_gravity="center_vertical"
android:textStyle="bold" />
</FrameLayout>

View File

@ -8,7 +8,6 @@
android:theme="@style/AppTheme.Overlay.Dark"
android:background="@color/colorPrimary"
android:visibility="gone"
app:elevation="4dp"
app:tabGravity="center"
app:tabMode="scrollable"
app:tabMinWidth="75dp"

View File

@ -8,5 +8,4 @@
android:theme="@style/AppTheme.Overlay.Dark"
app:tabGravity="fill"
android:background="@color/colorPrimary"
app:elevation="4dp"
app:tabIndicatorColor="@color/white"/>

View File

@ -1,10 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:elevation="4dp"
android:theme="@style/AppTheme.ActionBar"
app:layout_scrollFlags="scroll|enterAlways|snap"/>
android:theme="@style/AppTheme.ActionBar"/>

View File

@ -1,11 +1,16 @@
<menu 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" tools:context=".CatalogueListActivity">
<item
android:id="@+id/action_search"
android:title="@string/action_search"
android:icon="@drawable/ic_action_search"
android:orderInCategory="100"
app:showAsAction="collapseActionView|ifRoom"
app:actionViewClass="android.support.v7.widget.SearchView"/>
<item
android:id="@+id/action_display_mode"
android:title="@string/action_display_mode"
app:showAsAction="ifRoom"/>
</menu>

View File

@ -22,4 +22,9 @@
android:title="@string/action_mark_as_unread"
android:icon="@drawable/ic_action_undone_all"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_mark_previous_as_read"
android:title="@string/action_mark_previous_as_read"/>
</menu>

View File

@ -1,4 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="alertDialogTheme">@style/AlertDialogStyle</item>
<item name="android:textColor">@color/primary_text</item>
<item name="windowActionModeOverlay">true</item>
<item name="android:navigationBarColor">@color/colorPrimaryDark</item>
</style>
</resources>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorAccent">@color/md_blue_A400</color>
<color name="colorPrimary">@color/md_blue_grey_500</color>
<color name="colorPrimaryDark">@color/md_blue_grey_700</color>
<color name="colorPrimary">#54759e</color>
<color name="colorPrimaryDark">#435e7e</color>
<color name="colorPrimarySuperDark">@color/md_blue_grey_900</color>
<color name="colorPrimaryLight">@color/md_blue_grey_100</color>

View File

@ -35,4 +35,6 @@
<string name="pref_version">pref_version</string>
<string name="pref_build_time">pref_build_time</string>
<string name="pref_display_catalogue_as_list">pref_display_catalogue_as_list</string>
</resources>

View File

@ -19,6 +19,7 @@
<string name="action_select_all">Select all</string>
<string name="action_mark_as_read">Mark as read</string>
<string name="action_mark_as_unread">Mark as unread</string>
<string name="action_mark_previous_as_read">Mark previous as read</string>
<string name="action_download">Download</string>
<string name="action_delete">Delete</string>
<string name="action_update">Update</string>
@ -37,6 +38,7 @@
<string name="action_previous_chapter">Previous chapter</string>
<string name="action_next_chapter">Next chapter</string>
<string name="action_retry">Retry</string>
<string name="action_display_mode">Change display mode</string>
<!-- Buttons -->
@ -64,7 +66,7 @@
<string name="landscape">Landscape</string>
<string name="default_columns">Default</string>
<string name="pref_library_update_interval">Library update period</string>
<string name="pref_update_only_non_completed">Update only non completed manga</string>
<string name="pref_update_only_non_completed">Only update uncomplete manga</string>
<string name="update_never">Manual</string>
<string name="update_1hour">Hourly</string>
<string name="update_2hour">Every 2 hours</string>
@ -99,16 +101,16 @@
<!-- Downloads section -->
<string name="pref_download_directory">Downloads directory</string>
<string name="pref_download_slots">Simultaneous downloads</string>
<string name="pref_download_only_over_wifi">Download only over Wi-Fi</string>
<string name="pref_download_only_over_wifi">Only download over Wi-Fi</string>
<!-- Advanced section -->
<string name="pref_clear_chapter_cache">Clear chapter cache</string>
<string name="used_cache">Used: %1$s</string>
<string name="cache_deleted">Cache cleared. %1$d files have been deleted</string>
<string name="cache_delete_error">An error occurred clearing cache</string>
<string name="cache_delete_error">An error occurred while clearing cache</string>
<string name="pref_clear_database">Clear database</string>
<string name="pref_clear_database_summary">Delete mangas and chapters that are not in your library</string>
<string name="clear_database_confirmation">Are you sure? Read chapters and progress of non library mangas will be lost</string>
<string name="pref_clear_database_summary">Delete manga and chapters that are not in your library</string>
<string name="clear_database_confirmation">Are you sure? Read chapters and progress of non-library manga will be lost</string>
<!-- About section -->
<string name="version">Version</string>
@ -116,7 +118,7 @@
<!-- ACRA -->
<string name="pref_enable_acra">Send crash reports</string>
<string name="pref_acra_summary">Help fixing bugs. Sensitive data is not sent</string>
<string name="pref_acra_summary">Helps fixing bugs. No sensitive data is sent</string>
<!-- Login dialog -->
@ -142,6 +144,7 @@
<string name="author">Author</string>
<string name="chapters">Chapters</string>
<string name="genres">Genres</string>
<string name="source">Source</string>
<string name="artist">Artist</string>
<string name="description">Description</string>
<string name="status">Status</string>
@ -178,7 +181,7 @@
<string name="chapter_subtitle">Chapter %1$s</string>
<string name="no_next_chapter">Next chapter not found</string>
<string name="no_previous_chapter">Previous chapter not found</string>
<string name="decode_image_error">The image could not be loaded.\nTry to change the image decoder</string>
<string name="decode_image_error">Image could not be loaded.\nTry changing the image decoder</string>
<string name="confirm_update_manga_sync">Update last chapter read in enabled services to %1$d?</string>
<!-- Downloads activity and service -->
@ -187,9 +190,9 @@
<!-- Library update service notifications -->
<string name="notification_update_progress">Update progress: %1$d/%2$d</string>
<string name="notification_update_completed">Update completed</string>
<string name="notification_update_error">An unexpected error occurred updating the library</string>
<string name="notification_update_error">An unexpected error occurred while updating the library</string>
<string name="notification_no_new_chapters">No new chapters found</string>
<string name="notification_new_chapters">Found new chapters for:</string>
<string name="notification_manga_update_failed">Failed updating mangas:</string>
<string name="notification_manga_update_failed">Failed updating manga:</string>
</resources>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
<SwitchPreference
android:key="acra.enable"
android:title="@string/pref_enable_acra"
android:summary="@string/pref_acra_summary"

View File

@ -5,7 +5,7 @@
android:title="@string/pref_download_directory"
android:key="@string/pref_download_directory_key"/>
<CheckBoxPreference
<SwitchPreference
android:title="@string/pref_download_only_over_wifi"
android:key="@string/pref_download_only_over_wifi_key"
android:defaultValue="true"/>

View File

@ -14,17 +14,17 @@
android:defaultValue="0"
android:summary="%s"/>
<CheckBoxPreference
<SwitchPreference
android:key="@string/pref_update_only_non_completed_key"
android:title="@string/pref_update_only_non_completed"
android:defaultValue="false"/>
<CheckBoxPreference
<SwitchPreference
android:key="@string/pref_auto_update_manga_sync_key"
android:title="@string/pref_auto_update_manga_sync"
android:defaultValue="true"/>
<CheckBoxPreference
<SwitchPreference
android:key="@string/pref_ask_update_manga_sync_key"
android:title="@string/pref_ask_update_manga_sync"
android:defaultValue="false"

View File

@ -1,26 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference android:title="@string/pref_hide_status_bar"
<SwitchPreference android:title="@string/pref_hide_status_bar"
android:key="@string/pref_hide_status_bar_key"
android:defaultValue="true" />
<CheckBoxPreference android:title="@string/pref_lock_orientation"
<SwitchPreference android:title="@string/pref_lock_orientation"
android:key="@string/pref_lock_orientation_key"
android:defaultValue="true" />
<CheckBoxPreference android:title="@string/pref_enable_transitions"
<SwitchPreference android:title="@string/pref_enable_transitions"
android:key="@string/pref_enable_transitions_key"
android:defaultValue="true" />
<CheckBoxPreference android:title="@string/pref_show_page_number"
<SwitchPreference android:title="@string/pref_show_page_number"
android:key="@string/pref_show_page_number_key"
android:defaultValue="true" />
<CheckBoxPreference android:title="@string/pref_custom_brightness"
android:key="@string/pref_custom_brightness_key"
android:defaultValue="false" />
<eu.kanade.tachiyomi.widget.preference.IntListPreference
android:title="@string/pref_viewer_type"
android:key="@string/pref_default_viewer_key"

View File

@ -1,40 +0,0 @@
package eu.kanade.tachiyomi;
/**
* Created by len on 1/10/15.
*/
import android.os.Build;
import android.support.v7.widget.Toolbar;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;
import eu.kanade.tachiyomi.ui.main.MainActivity;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP)
@RunWith(RobolectricGradleTestRunner.class)
public class MainActivityTest {
private MainActivity activity;
// @Before => JUnit 4 annotation that specifies this method should run before each test is run
// Useful to do setup for objects that are needed in the test
@Before
public void setup() {
// Convenience method to run MainActivity through the Activity Lifecycle methods:
// onCreate(...) => onStart() => onPostCreate(...) => onResume()
activity = Robolectric.setupActivity(MainActivity.class);
}
@Test
public void validate() {
Toolbar toolbar = (Toolbar)activity.findViewById(R.id.toolbar);
assertNotNull(toolbar);
}
}

View File

@ -20,9 +20,6 @@ allprojects {
repositories {
jcenter()
maven { url "https://clojars.org/repo/" }
maven { url "http://dl.bintray.com/davideas/maven" }
maven { url "https://jitpack.io" }
maven { url 'http://dl.bintray.com/amulyakhare/maven' }
maven { url 'https://github.com/suckgamony/RapidDecoder/raw/master/repository' }
}
}

View File

@ -6,9 +6,9 @@ version = '3.4.1'
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile "com.android.support:support-annotations:23.1.1"
compile 'rapid.decoder:library:0.3.0'
compile 'rapid.decoder:jpeg-decoder:0.3.0'
compile 'rapid.decoder:png-decoder:0.3.0'
compile 'com.github.suckgamony.RapidDecoder:library:7cdfca4'
compile 'com.github.suckgamony.RapidDecoder:jpeg-decoder:7cdfca4'
compile 'com.github.suckgamony.RapidDecoder:png-decoder:7cdfca4'
}
android {