Compare commits
76 Commits
Author | SHA1 | Date | |
---|---|---|---|
110df59197 | |||
75ae4081d8 | |||
2efca050b3 | |||
37e119c4f2 | |||
9786074119 | |||
d4876b426f | |||
8581e4667a | |||
b94f86765d | |||
ba0f3778ce | |||
aac6b242a0 | |||
dec9442a65 | |||
b33da641d9 | |||
96d498e7e5 | |||
eee137a084 | |||
5e834ae3be | |||
dcfda61aba | |||
5ac7f7057a | |||
ff46c61f63 | |||
57b64a412e | |||
1e81f75377 | |||
1dd49a2ab1 | |||
1cd77a97a7 | |||
f820522a69 | |||
3da613dedb | |||
5c329d2314 | |||
4c073e713d | |||
2832f4ae5e | |||
7690e8a53f | |||
3a19f8e40b | |||
a33b525f9e | |||
7d6ce46829 | |||
a90a4bf80c | |||
140bf8caee | |||
56a45f263e | |||
01d6ddfafb | |||
393b4916f6 | |||
cb3c3af865 | |||
5a83976fa5 | |||
a81f6c3ac4 | |||
6846ce5bfb | |||
0c0ebe06e5 | |||
e50c683159 | |||
872af276ea | |||
e6faee9779 | |||
bc1ddd4379 | |||
e348d6c1cf | |||
7835921045 | |||
1611a274b9 | |||
fa4a8204a4 | |||
5977e9f47f | |||
63d0161da5 | |||
d8b46c1969 | |||
f84731c2df | |||
50d71d1395 | |||
4be0b2502e | |||
6c069ad87b | |||
e69011ac5b | |||
ea130a0899 | |||
2566862e0f | |||
16081817c2 | |||
945625d3ad | |||
050b9c9fce | |||
c35184abdc | |||
34c5f0b7ba | |||
6435eeb251 | |||
eec2dcd981 | |||
57ba368ae0 | |||
ed06469885 | |||
79cd8c691e | |||
391550f49a | |||
6aa07dd17e | |||
aada373a0c | |||
3deac86bbe | |||
d7aef2e97a | |||
7953ba6e78 | |||
8aa3c2a260 |
6
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
**Please read https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md before posting**
|
||||||
|
|
||||||
|
Remove line above and describe your issue here. Fill out version below.
|
||||||
|
|
||||||
|
---
|
||||||
|
Version: r000 or v0.0.0
|
@ -1,13 +1,15 @@
|
|||||||
[](https://github.com/inorichi/tachiyomi/releases)
|
[](https://github.com/inorichi/tachiyomi/releases)
|
||||||
[](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi)
|
[](https://f-droid.org/repository/browse/?fdid=eu.kanade.tachiyomi)
|
||||||
[](http://tachiyomi.kanade.eu/latest/app-debug.apk)
|
[](http://tachiyomi.kanade.eu/latest/app-debug.apk)
|
||||||
|
|
||||||
|
## [Report an issue](https://github.com/inorichi/tachiyomi/blob/master/.github/CONTRIBUTING.md)
|
||||||
|
|
||||||
|
**Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened issues.**
|
||||||
|
|
||||||
Tachiyomi is a free and open source manga reader for Android.
|
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.
|
Keep in mind it's still a beta, so expect it to crash sometimes.
|
||||||
|
|
||||||
Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/inorichi/tachiyomi/releases) and the already opened issues.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Online and offline reading
|
* Online and offline reading
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
apply plugin: 'android-sdk-manager'
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'com.neenbedankt.android-apt'
|
apply plugin: 'com.neenbedankt.android-apt'
|
||||||
apply plugin: 'me.tatarka.retrolambda'
|
apply plugin: 'me.tatarka.retrolambda'
|
||||||
@ -29,6 +28,10 @@ ext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def includeUpdater() {
|
||||||
|
return hasProperty("include_updater");
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 23
|
compileSdkVersion 23
|
||||||
buildToolsVersion "23.0.2"
|
buildToolsVersion "23.0.2"
|
||||||
@ -39,12 +42,13 @@ android {
|
|||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 23
|
targetSdkVersion 23
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
versionCode 4
|
versionCode 5
|
||||||
versionName "0.1.3"
|
versionName "0.1.4"
|
||||||
|
|
||||||
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
|
||||||
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
|
||||||
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
buildConfigField "String", "BUILD_TIME", "\"${getBuildTime()}\""
|
||||||
|
buildConfigField "boolean", "INCLUDE_UPDATER", "${includeUpdater()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@ -78,16 +82,24 @@ android {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apt {
|
||||||
|
arguments {
|
||||||
|
eventBusIndex "eu.kanade.tachiyomi.EventBusIndex"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
final SUPPORT_LIBRARY_VERSION = '23.1.1'
|
final SUPPORT_LIBRARY_VERSION = '23.1.1'
|
||||||
final DAGGER_VERSION = '2.0.2'
|
final DAGGER_VERSION = '2.0.2'
|
||||||
final OKHTTP_VERSION = '3.0.1'
|
final EVENTBUS_VERSION = '3.0.0'
|
||||||
final MOCKITO_VERSION = '1.10.19'
|
final OKHTTP_VERSION = '3.1.1'
|
||||||
final STORIO_VERSION = '1.8.0'
|
final STORIO_VERSION = '1.8.0'
|
||||||
final ICEPICK_VERSION = '3.1.0'
|
final ICEPICK_VERSION = '3.1.0'
|
||||||
|
final MOCKITO_VERSION = '1.10.19'
|
||||||
|
|
||||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
compile project(":SubsamplingScaleImageView")
|
compile project(":SubsamplingScaleImageView")
|
||||||
|
compile project(":ReactiveNetwork")
|
||||||
|
|
||||||
compile "com.android.support:support-v4:$SUPPORT_LIBRARY_VERSION"
|
compile "com.android.support:support-v4:$SUPPORT_LIBRARY_VERSION"
|
||||||
compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION"
|
compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION"
|
||||||
@ -104,23 +116,24 @@ dependencies {
|
|||||||
compile 'org.jsoup:jsoup:1.8.3'
|
compile 'org.jsoup:jsoup:1.8.3'
|
||||||
compile 'io.reactivex:rxandroid:1.1.0'
|
compile 'io.reactivex:rxandroid:1.1.0'
|
||||||
compile 'io.reactivex:rxjava:1.1.0'
|
compile 'io.reactivex:rxjava:1.1.0'
|
||||||
|
compile 'com.squareup.retrofit:retrofit:1.9.0'
|
||||||
compile 'com.f2prateek.rx.preferences:rx-preferences:1.0.1'
|
compile 'com.f2prateek.rx.preferences:rx-preferences:1.0.1'
|
||||||
compile "com.pushtorefresh.storio:sqlite:$STORIO_VERSION"
|
compile "com.pushtorefresh.storio:sqlite:$STORIO_VERSION"
|
||||||
compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION"
|
compile "com.pushtorefresh.storio:sqlite-annotations:$STORIO_VERSION"
|
||||||
compile 'info.android15.nucleus:nucleus:2.0.4'
|
compile 'info.android15.nucleus:nucleus:2.0.4'
|
||||||
compile 'de.greenrobot:eventbus:2.4.0'
|
|
||||||
compile 'com.github.bumptech.glide:glide:3.6.1'
|
compile 'com.github.bumptech.glide:glide:3.6.1'
|
||||||
compile 'com.jakewharton:butterknife:7.0.1'
|
compile 'com.jakewharton:butterknife:7.0.1'
|
||||||
compile 'com.jakewharton.timber:timber:4.1.0'
|
compile 'com.jakewharton.timber:timber:4.1.0'
|
||||||
compile 'uk.co.ribot:easyadapter:1.5.0@aar'
|
compile 'ch.acra:acra:4.8.1'
|
||||||
compile 'ch.acra:acra:4.7.0'
|
|
||||||
compile "frankiesardo:icepick:$ICEPICK_VERSION"
|
compile "frankiesardo:icepick:$ICEPICK_VERSION"
|
||||||
provided "frankiesardo:icepick-processor:$ICEPICK_VERSION"
|
provided "frankiesardo:icepick-processor:$ICEPICK_VERSION"
|
||||||
compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
compile 'com.github.dmytrodanylyk.android-process-button:library:1.0.4'
|
||||||
compile 'eu.davidea:flexible-adapter:4.2.0'
|
compile 'eu.davidea:flexible-adapter:4.2.0'
|
||||||
compile 'com.nononsenseapps:filepicker:2.5.1'
|
compile 'com.nononsenseapps:filepicker:2.5.1'
|
||||||
compile 'com.github.amulyakhare:TextDrawable:558677e'
|
compile 'com.github.amulyakhare:TextDrawable:558677e'
|
||||||
compile 'com.github.pwittchen:reactivenetwork:0.1.5'
|
|
||||||
|
compile "org.greenrobot:eventbus:$EVENTBUS_VERSION"
|
||||||
|
apt "org.greenrobot:eventbus-annotation-processor:$EVENTBUS_VERSION"
|
||||||
|
|
||||||
compile "com.google.dagger:dagger:$DAGGER_VERSION"
|
compile "com.google.dagger:dagger:$DAGGER_VERSION"
|
||||||
apt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
apt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||||
@ -131,10 +144,10 @@ dependencies {
|
|||||||
transitive = true
|
transitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
//Google material icons SVG.
|
// Google material icons SVG.
|
||||||
compile 'com.mikepenz:google-material-typeface:2.1.0.1.original@aar'
|
compile 'com.mikepenz:google-material-typeface:2.1.0.1.original@aar'
|
||||||
|
|
||||||
compile('com.github.afollestad.material-dialogs:core:0.8.5.3@aar') {
|
compile('com.github.afollestad.material-dialogs:core:0.8.5.4@aar') {
|
||||||
transitive = true
|
transitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
34
app/proguard-rules.pro
vendored
@ -39,14 +39,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
## GreenRobot EventBus specific rules ##
|
## GreenRobot EventBus specific rules ##
|
||||||
# https://github.com/greenrobot/EventBus/blob/master/HOWTO.md#proguard-configuration
|
# http://greenrobot.org/eventbus/documentation/proguard/
|
||||||
|
-keepattributes *Annotation*
|
||||||
-keepclassmembers class ** {
|
-keepclassmembers class ** {
|
||||||
public void onEvent*(***);
|
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||||
}
|
}
|
||||||
|
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||||
|
|
||||||
# Don't warn for missing support classes
|
# Only required if you use AsyncExecutor
|
||||||
-dontwarn de.greenrobot.event.util.*$Support
|
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
|
||||||
-dontwarn de.greenrobot.event.util.*$SupportManagerFragment
|
<init>(java.lang.Throwable);
|
||||||
|
}
|
||||||
|
|
||||||
# Glide specific rules #
|
# Glide specific rules #
|
||||||
# https://github.com/bumptech/glide
|
# https://github.com/bumptech/glide
|
||||||
@ -73,6 +76,27 @@
|
|||||||
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
rx.internal.util.atomic.LinkedQueueNode consumerNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Retrofit 1.X
|
||||||
|
|
||||||
|
-keep class com.squareup.okhttp.** { *; }
|
||||||
|
-keep class retrofit.** { *; }
|
||||||
|
-keep interface com.squareup.okhttp.** { *; }
|
||||||
|
|
||||||
|
-dontwarn com.squareup.okhttp.**
|
||||||
|
-dontwarn okio.**
|
||||||
|
-dontwarn retrofit.**
|
||||||
|
-dontwarn rx.**
|
||||||
|
|
||||||
|
-keepclasseswithmembers class * {
|
||||||
|
@retrofit.http.* <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# If in your rest service interface you use methods with Callback argument.
|
||||||
|
-keepattributes Exceptions
|
||||||
|
|
||||||
|
# If your rest service methods throw custom exceptions, because you've defined an ErrorHandler.
|
||||||
|
-keepattributes Signature
|
||||||
|
|
||||||
# AppCombat
|
# AppCombat
|
||||||
-keep public class android.support.v7.widget.** { *; }
|
-keep public class android.support.v7.widget.** { *; }
|
||||||
-keep public class android.support.v7.internal.widget.** { *; }
|
-keep public class android.support.v7.internal.widget.** { *; }
|
||||||
|
@ -5,6 +5,7 @@ import android.content.Context;
|
|||||||
|
|
||||||
import org.acra.ACRA;
|
import org.acra.ACRA;
|
||||||
import org.acra.annotation.ReportsCrashes;
|
import org.acra.annotation.ReportsCrashes;
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.injection.ComponentReflectionInjector;
|
import eu.kanade.tachiyomi.injection.ComponentReflectionInjector;
|
||||||
import eu.kanade.tachiyomi.injection.component.AppComponent;
|
import eu.kanade.tachiyomi.injection.component.AppComponent;
|
||||||
@ -23,6 +24,10 @@ public class App extends Application {
|
|||||||
AppComponent applicationComponent;
|
AppComponent applicationComponent;
|
||||||
ComponentReflectionInjector<AppComponent> componentInjector;
|
ComponentReflectionInjector<AppComponent> componentInjector;
|
||||||
|
|
||||||
|
public static App get(Context context) {
|
||||||
|
return (App) context.getApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
@ -35,23 +40,28 @@ public class App extends Application {
|
|||||||
componentInjector =
|
componentInjector =
|
||||||
new ComponentReflectionInjector<>(AppComponent.class, applicationComponent);
|
new ComponentReflectionInjector<>(AppComponent.class, applicationComponent);
|
||||||
|
|
||||||
|
setupEventBus();
|
||||||
|
|
||||||
ACRA.init(this);
|
ACRA.init(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static App get(Context context) {
|
protected void setupEventBus() {
|
||||||
return (App) context.getApplicationContext();
|
EventBus.builder()
|
||||||
|
.addIndex(new EventBusIndex())
|
||||||
|
.logNoSubscriberMessages(false)
|
||||||
|
.installDefaultEventBus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public AppComponent getComponent() {
|
public AppComponent getComponent() {
|
||||||
return applicationComponent;
|
return applicationComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComponentReflectionInjector<AppComponent> getComponentReflection() {
|
|
||||||
return componentInjector;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Needed to replace the component with a test specific one
|
// Needed to replace the component with a test specific one
|
||||||
public void setComponent(AppComponent applicationComponent) {
|
public void setComponent(AppComponent applicationComponent) {
|
||||||
this.applicationComponent = applicationComponent;
|
this.applicationComponent = applicationComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ComponentReflectionInjector<AppComponent> getComponentReflection() {
|
||||||
|
return componentInjector;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import com.pushtorefresh.storio.sqlite.queries.RawQuery;
|
|||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category;
|
import eu.kanade.tachiyomi.data.database.models.Category;
|
||||||
import eu.kanade.tachiyomi.data.database.models.CategorySQLiteTypeMapping;
|
import eu.kanade.tachiyomi.data.database.models.CategorySQLiteTypeMapping;
|
||||||
@ -259,21 +260,32 @@ public class DatabaseHelper {
|
|||||||
int deleted = 0;
|
int deleted = 0;
|
||||||
db.internal().beginTransaction();
|
db.internal().beginTransaction();
|
||||||
try {
|
try {
|
||||||
|
TreeSet<Float> deletedReadChapterNumbers = new TreeSet<>();
|
||||||
|
if (!toDelete.isEmpty()) {
|
||||||
|
for (Chapter c : toDelete) {
|
||||||
|
if (c.read) {
|
||||||
|
deletedReadChapterNumbers.add(c.chapter_number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleted = deleteChapters(toDelete).executeAsBlocking().results().size();
|
||||||
|
}
|
||||||
|
|
||||||
if (!toAdd.isEmpty()) {
|
if (!toAdd.isEmpty()) {
|
||||||
// Set the date fetch for new items in reverse order to allow another sorting method.
|
// Set the date fetch for new items in reverse order to allow another sorting method.
|
||||||
// Sources MUST return the chapters from most to less recent, which is common.
|
// Sources MUST return the chapters from most to less recent, which is common.
|
||||||
long now = new Date().getTime();
|
long now = new Date().getTime();
|
||||||
|
|
||||||
for (int i = toAdd.size() - 1; i >= 0; i--) {
|
for (int i = toAdd.size() - 1; i >= 0; i--) {
|
||||||
toAdd.get(i).date_fetch = now++;
|
Chapter c = toAdd.get(i);
|
||||||
|
c.date_fetch = now++;
|
||||||
|
// Try to mark already read chapters as read when the source deletes them
|
||||||
|
if (c.chapter_number != -1 && deletedReadChapterNumbers.contains(c.chapter_number)) {
|
||||||
|
c.read = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
added = insertChapters(toAdd).executeAsBlocking().numberOfInserts();
|
added = insertChapters(toAdd).executeAsBlocking().numberOfInserts();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!toDelete.isEmpty()) {
|
|
||||||
deleted = deleteChapters(toDelete).executeAsBlocking().results().size();
|
|
||||||
}
|
|
||||||
|
|
||||||
db.internal().setTransactionSuccessful();
|
db.internal().setTransactionSuccessful();
|
||||||
} finally {
|
} finally {
|
||||||
db.internal().endTransaction();
|
db.internal().endTransaction();
|
||||||
|
@ -4,8 +4,11 @@ import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn;
|
|||||||
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable;
|
||||||
|
import eu.kanade.tachiyomi.data.download.model.Download;
|
||||||
|
import eu.kanade.tachiyomi.data.source.model.Page;
|
||||||
import eu.kanade.tachiyomi.util.UrlUtil;
|
import eu.kanade.tachiyomi.util.UrlUtil;
|
||||||
|
|
||||||
@StorIOSQLiteType(table = ChapterTable.TABLE)
|
@StorIOSQLiteType(table = ChapterTable.TABLE)
|
||||||
@ -40,6 +43,8 @@ public class Chapter implements Serializable {
|
|||||||
|
|
||||||
public int status;
|
public int status;
|
||||||
|
|
||||||
|
private transient List<Page> pages;
|
||||||
|
|
||||||
public Chapter() {}
|
public Chapter() {}
|
||||||
|
|
||||||
public void setUrl(String url) {
|
public void setUrl(String url) {
|
||||||
@ -68,4 +73,15 @@ public class Chapter implements Serializable {
|
|||||||
return chapter;
|
return chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Page> getPages() {
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPages(List<Page> pages) {
|
||||||
|
this.pages = pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDownloaded() {
|
||||||
|
return status == Download.DOWNLOADED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ public class DownloadManager {
|
|||||||
if (finished) {
|
if (finished) {
|
||||||
DownloadService.stop(context);
|
DownloadService.stop(context);
|
||||||
}
|
}
|
||||||
}, e -> Timber.e(e.getCause(), e.getMessage()));
|
}, e -> DownloadService.stop(context));
|
||||||
|
|
||||||
if (!isRunning) {
|
if (!isRunning) {
|
||||||
isRunning = true;
|
isRunning = true;
|
||||||
|
@ -8,14 +8,16 @@ import android.os.PowerManager;
|
|||||||
|
|
||||||
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork;
|
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import de.greenrobot.event.EventBus;
|
|
||||||
import eu.kanade.tachiyomi.App;
|
import eu.kanade.tachiyomi.App;
|
||||||
import eu.kanade.tachiyomi.R;
|
import eu.kanade.tachiyomi.R;
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
|
||||||
import eu.kanade.tachiyomi.event.DownloadChaptersEvent;
|
import eu.kanade.tachiyomi.event.DownloadChaptersEvent;
|
||||||
import eu.kanade.tachiyomi.util.EventBusHook;
|
|
||||||
import eu.kanade.tachiyomi.util.ToastUtil;
|
import eu.kanade.tachiyomi.util.ToastUtil;
|
||||||
import rx.Subscription;
|
import rx.Subscription;
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
@ -47,7 +49,7 @@ public class DownloadService extends Service {
|
|||||||
createWakeLock();
|
createWakeLock();
|
||||||
|
|
||||||
listenQueueRunningChanges();
|
listenQueueRunningChanges();
|
||||||
EventBus.getDefault().registerSticky(this);
|
EventBus.getDefault().register(this);
|
||||||
listenNetworkChanges();
|
listenNetworkChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +73,7 @@ public class DownloadService extends Service {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventBusHook
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
public void onEvent(DownloadChaptersEvent event) {
|
public void onEvent(DownloadChaptersEvent event) {
|
||||||
EventBus.getDefault().removeStickyEvent(event);
|
EventBus.getDefault().removeStickyEvent(event);
|
||||||
downloadManager.onDownloadChaptersEvent(event);
|
downloadManager.onDownloadChaptersEvent(event);
|
||||||
|
@ -83,7 +83,7 @@ public class MyAnimeList extends MangaSyncService {
|
|||||||
|
|
||||||
public Observable<Boolean> login(String username, String password) {
|
public Observable<Boolean> login(String username, String password) {
|
||||||
createHeaders(username, password);
|
createHeaders(username, password);
|
||||||
return networkService.getResponse(getLoginUrl(), headers, null)
|
return networkService.getResponse(getLoginUrl(), headers, false)
|
||||||
.map(response -> response.code() == 200);
|
.map(response -> response.code() == 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ public class MyAnimeList extends MangaSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Observable<List<MangaSync>> search(String query) {
|
public Observable<List<MangaSync>> search(String query) {
|
||||||
return networkService.getStringResponse(getSearchUrl(query), headers, null)
|
return networkService.getStringResponse(getSearchUrl(query), headers, true)
|
||||||
.map(Jsoup::parse)
|
.map(Jsoup::parse)
|
||||||
.flatMap(doc -> Observable.from(doc.select("entry")))
|
.flatMap(doc -> Observable.from(doc.select("entry")))
|
||||||
.filter(entry -> !entry.select("type").text().equals("Novel"))
|
.filter(entry -> !entry.select("type").text().equals("Novel"))
|
||||||
@ -126,7 +126,7 @@ public class MyAnimeList extends MangaSyncService {
|
|||||||
|
|
||||||
public Observable<List<MangaSync>> getList() {
|
public Observable<List<MangaSync>> getList() {
|
||||||
// TODO cache this list for a few minutes
|
// TODO cache this list for a few minutes
|
||||||
return networkService.getStringResponse(getListUrl(username), headers, null)
|
return networkService.getStringResponse(getListUrl(username), headers, true)
|
||||||
.map(Jsoup::parse)
|
.map(Jsoup::parse)
|
||||||
.flatMap(doc -> Observable.from(doc.select("manga")))
|
.flatMap(doc -> Observable.from(doc.select("manga")))
|
||||||
.map(entry -> {
|
.map(entry -> {
|
||||||
|
@ -7,11 +7,13 @@ import java.io.File;
|
|||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
import java.net.CookiePolicy;
|
import java.net.CookiePolicy;
|
||||||
import java.net.CookieStore;
|
import java.net.CookieStore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import okhttp3.Cache;
|
import okhttp3.Cache;
|
||||||
import okhttp3.CacheControl;
|
import okhttp3.CacheControl;
|
||||||
import okhttp3.FormBody;
|
import okhttp3.FormBody;
|
||||||
import okhttp3.Headers;
|
import okhttp3.Headers;
|
||||||
|
import okhttp3.Interceptor;
|
||||||
import okhttp3.JavaNetCookieJar;
|
import okhttp3.JavaNetCookieJar;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
@ -22,12 +24,23 @@ import rx.Observable;
|
|||||||
public final class NetworkHelper {
|
public final class NetworkHelper {
|
||||||
|
|
||||||
private OkHttpClient client;
|
private OkHttpClient client;
|
||||||
|
private OkHttpClient forceCacheClient;
|
||||||
|
|
||||||
private CookieManager cookieManager;
|
private CookieManager cookieManager;
|
||||||
|
|
||||||
public final CacheControl NULL_CACHE_CONTROL = new CacheControl.Builder().noCache().build();
|
|
||||||
public final Headers NULL_HEADERS = new Headers.Builder().build();
|
public final Headers NULL_HEADERS = new Headers.Builder().build();
|
||||||
public final RequestBody NULL_REQUEST_BODY = new FormBody.Builder().build();
|
public final RequestBody NULL_REQUEST_BODY = new FormBody.Builder().build();
|
||||||
|
public final CacheControl CACHE_CONTROL = new CacheControl.Builder()
|
||||||
|
.maxAge(10, TimeUnit.MINUTES)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = chain -> {
|
||||||
|
Response originalResponse = chain.proceed(chain.request());
|
||||||
|
return originalResponse.newBuilder()
|
||||||
|
.removeHeader("Pragma")
|
||||||
|
.header("Cache-Control", "max-age=" + 600)
|
||||||
|
.build();
|
||||||
|
};
|
||||||
|
|
||||||
private static final int CACHE_SIZE = 5 * 1024 * 1024; // 5 MiB
|
private static final int CACHE_SIZE = 5 * 1024 * 1024; // 5 MiB
|
||||||
private static final String CACHE_DIR_NAME = "network_cache";
|
private static final String CACHE_DIR_NAME = "network_cache";
|
||||||
@ -42,18 +55,24 @@ public final class NetworkHelper {
|
|||||||
.cookieJar(new JavaNetCookieJar(cookieManager))
|
.cookieJar(new JavaNetCookieJar(cookieManager))
|
||||||
.cache(new Cache(cacheDir, CACHE_SIZE))
|
.cache(new Cache(cacheDir, CACHE_SIZE))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
forceCacheClient = client.newBuilder()
|
||||||
|
.addNetworkInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Observable<Response> getResponse(final String url, final Headers headers, final CacheControl cacheControl) {
|
public Observable<Response> getResponse(final String url, final Headers headers, boolean forceCache) {
|
||||||
return Observable.defer(() -> {
|
return Observable.defer(() -> {
|
||||||
try {
|
try {
|
||||||
|
OkHttpClient c = forceCache ? forceCacheClient : client;
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.cacheControl(cacheControl != null ? cacheControl : NULL_CACHE_CONTROL)
|
|
||||||
.headers(headers != null ? headers : NULL_HEADERS)
|
.headers(headers != null ? headers : NULL_HEADERS)
|
||||||
|
.cacheControl(CACHE_CONTROL)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return Observable.just(client.newCall(request).execute());
|
return Observable.just(c.newCall(request).execute());
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
return Observable.error(e);
|
return Observable.error(e);
|
||||||
}
|
}
|
||||||
@ -70,8 +89,8 @@ public final class NetworkHelper {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Observable<String> getStringResponse(final String url, final Headers headers, final CacheControl cacheControl) {
|
public Observable<String> getStringResponse(final String url, final Headers headers, boolean forceCache) {
|
||||||
return getResponse(url, headers, cacheControl)
|
return getResponse(url, headers, forceCache)
|
||||||
.flatMap(this::mapResponseToString);
|
.flatMap(this::mapResponseToString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +114,7 @@ public final class NetworkHelper {
|
|||||||
try {
|
try {
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.cacheControl(NULL_CACHE_CONTROL)
|
.cacheControl(CacheControl.FORCE_NETWORK)
|
||||||
.headers(headers != null ? headers : NULL_HEADERS)
|
.headers(headers != null ? headers : NULL_HEADERS)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -42,10 +42,12 @@ public class PreferencesHelper {
|
|||||||
if (getDownloadsDirectory().equals(defaultDownloadsDir.getAbsolutePath()) &&
|
if (getDownloadsDirectory().equals(defaultDownloadsDir.getAbsolutePath()) &&
|
||||||
!defaultDownloadsDir.exists()) {
|
!defaultDownloadsDir.exists()) {
|
||||||
defaultDownloadsDir.mkdirs();
|
defaultDownloadsDir.mkdirs();
|
||||||
try {
|
|
||||||
new File(defaultDownloadsDir, ".nomedia").createNewFile();
|
|
||||||
} catch (IOException e) { /* Ignore */ }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't display downloaded chapters in gallery apps creating a ".nomedia" file
|
||||||
|
try {
|
||||||
|
new File(getDownloadsDirectory(), ".nomedia").createNewFile();
|
||||||
|
} catch (IOException e) { /* Ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getKey(int keyResource) {
|
private String getKey(int keyResource) {
|
||||||
@ -56,8 +58,8 @@ public class PreferencesHelper {
|
|||||||
prefs.edit().clear().apply();
|
prefs.edit().clear().apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Preference<Boolean> lockOrientation() {
|
public Preference<Integer> rotation() {
|
||||||
return rxPrefs.getBoolean(getKey(R.string.pref_lock_orientation_key), true);
|
return rxPrefs.getInteger(getKey(R.string.pref_rotation_type_key), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Preference<Boolean> enableTransitions() {
|
public Preference<Boolean> enableTransitions() {
|
||||||
@ -92,6 +94,18 @@ public class PreferencesHelper {
|
|||||||
return rxPrefs.getInteger(getKey(R.string.pref_image_scale_type_key), 1);
|
return rxPrefs.getInteger(getKey(R.string.pref_image_scale_type_key), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Preference<Integer> imageDecoder() {
|
||||||
|
return rxPrefs.getInteger(getKey(R.string.pref_image_decoder_key), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Preference<Integer> zoomStart() {
|
||||||
|
return rxPrefs.getInteger(getKey(R.string.pref_zoom_start_key), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Preference<Integer> readerTheme() {
|
||||||
|
return rxPrefs.getInteger(getKey(R.string.pref_reader_theme_key), 0);
|
||||||
|
}
|
||||||
|
|
||||||
public Preference<Integer> portraitColumns() {
|
public Preference<Integer> portraitColumns() {
|
||||||
return rxPrefs.getInteger(getKey(R.string.pref_library_columns_portrait_key), 0);
|
return rxPrefs.getInteger(getKey(R.string.pref_library_columns_portrait_key), 0);
|
||||||
}
|
}
|
||||||
@ -112,12 +126,12 @@ public class PreferencesHelper {
|
|||||||
return prefs.getBoolean(getKey(R.string.pref_ask_update_manga_sync_key), false);
|
return prefs.getBoolean(getKey(R.string.pref_ask_update_manga_sync_key), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Preference<Integer> imageDecoder() {
|
public Preference<Integer> lastUsedCatalogueSource() {
|
||||||
return rxPrefs.getInteger(getKey(R.string.pref_image_decoder_key), 0);
|
return rxPrefs.getInteger(getKey(R.string.pref_last_catalogue_source_key), -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Preference<Integer> readerTheme() {
|
public boolean seamlessMode() {
|
||||||
return rxPrefs.getInteger(getKey(R.string.pref_reader_theme_key), 0);
|
return prefs.getBoolean(getKey(R.string.pref_seamless_mode_key), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Preference<Boolean> catalogueAsList() {
|
public Preference<Boolean> catalogueAsList() {
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.rest;
|
||||||
|
|
||||||
|
import retrofit.http.GET;
|
||||||
|
import rx.Observable;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to connect with the Github API
|
||||||
|
*/
|
||||||
|
public interface GithubService {
|
||||||
|
String SERVICE_ENDPOINT = "https://api.github.com";
|
||||||
|
|
||||||
|
@GET("/repos/inorichi/tachiyomi/releases/latest") Observable<Release> getLatestVersion();
|
||||||
|
|
||||||
|
}
|
93
app/src/main/java/eu/kanade/tachiyomi/data/rest/Release.java
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.rest;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release object
|
||||||
|
* Contains information about the latest release
|
||||||
|
*/
|
||||||
|
public class Release {
|
||||||
|
/**
|
||||||
|
* Version name V0.0.0
|
||||||
|
*/
|
||||||
|
@SerializedName("tag_name")
|
||||||
|
private final String version;
|
||||||
|
|
||||||
|
/** Change Log */
|
||||||
|
@SerializedName("body")
|
||||||
|
private final String log;
|
||||||
|
|
||||||
|
/** Assets containing download url */
|
||||||
|
@SerializedName("assets")
|
||||||
|
private final List<Assets> assets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release constructor
|
||||||
|
*
|
||||||
|
* @param version version of latest release
|
||||||
|
* @param log log of latest release
|
||||||
|
* @param assets assets of latest release
|
||||||
|
*/
|
||||||
|
public Release(String version, String log, List<Assets> assets) {
|
||||||
|
this.version = version;
|
||||||
|
this.log = log;
|
||||||
|
this.assets = assets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get latest release version
|
||||||
|
*
|
||||||
|
* @return latest release version
|
||||||
|
*/
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get change log of latest release
|
||||||
|
*
|
||||||
|
* @return change log of latest release
|
||||||
|
*/
|
||||||
|
public String getChangeLog() {
|
||||||
|
return log;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get download link of latest release
|
||||||
|
*
|
||||||
|
* @return download link of latest release
|
||||||
|
*/
|
||||||
|
public String getDownloadLink() {
|
||||||
|
return assets.get(0).getDownloadLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assets class containing download url
|
||||||
|
*/
|
||||||
|
class Assets {
|
||||||
|
@SerializedName("browser_download_url")
|
||||||
|
private final String download_url;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assets Constructor
|
||||||
|
*
|
||||||
|
* @param download_url download url
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused") public Assets(String download_url) {
|
||||||
|
this.download_url = download_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get download link of latest release
|
||||||
|
*
|
||||||
|
* @return download link of latest release
|
||||||
|
*/
|
||||||
|
public String getDownloadLink() {
|
||||||
|
return download_url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.rest;
|
||||||
|
|
||||||
|
import retrofit.RestAdapter;
|
||||||
|
|
||||||
|
public class ServiceFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a retrofit service from an arbitrary class (clazz)
|
||||||
|
*
|
||||||
|
* @param clazz Java interface of the retrofit service
|
||||||
|
* @param endPoint REST endpoint url
|
||||||
|
* @return retrofit service with defined endpoint
|
||||||
|
*/
|
||||||
|
public static <T> T createRetrofitService(final Class<T> clazz, final String endPoint) {
|
||||||
|
final RestAdapter restAdapter = new RestAdapter.Builder()
|
||||||
|
.setEndpoint(endPoint)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return restAdapter.create(clazz);
|
||||||
|
}
|
||||||
|
}
|
@ -53,7 +53,7 @@ public abstract class Source extends BaseSource {
|
|||||||
page.url = getInitialPopularMangasUrl();
|
page.url = getInitialPopularMangasUrl();
|
||||||
|
|
||||||
return networkService
|
return networkService
|
||||||
.getStringResponse(page.url, requestHeaders, null)
|
.getStringResponse(page.url, requestHeaders, true)
|
||||||
.map(Jsoup::parse)
|
.map(Jsoup::parse)
|
||||||
.doOnNext(doc -> page.mangas = parsePopularMangasFromHtml(doc))
|
.doOnNext(doc -> page.mangas = parsePopularMangasFromHtml(doc))
|
||||||
.doOnNext(doc -> page.nextPageUrl = parseNextPopularMangasUrl(doc, page))
|
.doOnNext(doc -> page.nextPageUrl = parseNextPopularMangasUrl(doc, page))
|
||||||
@ -66,7 +66,7 @@ public abstract class Source extends BaseSource {
|
|||||||
page.url = getInitialSearchUrl(query);
|
page.url = getInitialSearchUrl(query);
|
||||||
|
|
||||||
return networkService
|
return networkService
|
||||||
.getStringResponse(page.url, requestHeaders, null)
|
.getStringResponse(page.url, requestHeaders, true)
|
||||||
.map(Jsoup::parse)
|
.map(Jsoup::parse)
|
||||||
.doOnNext(doc -> page.mangas = parseSearchFromHtml(doc))
|
.doOnNext(doc -> page.mangas = parseSearchFromHtml(doc))
|
||||||
.doOnNext(doc -> page.nextPageUrl = parseNextSearchUrl(doc, page, query))
|
.doOnNext(doc -> page.nextPageUrl = parseNextSearchUrl(doc, page, query))
|
||||||
@ -76,14 +76,14 @@ public abstract class Source extends BaseSource {
|
|||||||
// Get manga details from the source
|
// Get manga details from the source
|
||||||
public Observable<Manga> pullMangaFromNetwork(final String mangaUrl) {
|
public Observable<Manga> pullMangaFromNetwork(final String mangaUrl) {
|
||||||
return networkService
|
return networkService
|
||||||
.getStringResponse(getBaseUrl() + overrideMangaUrl(mangaUrl), requestHeaders, null)
|
.getStringResponse(getBaseUrl() + overrideMangaUrl(mangaUrl), requestHeaders, true)
|
||||||
.flatMap(unparsedHtml -> Observable.just(parseHtmlToManga(mangaUrl, unparsedHtml)));
|
.flatMap(unparsedHtml -> Observable.just(parseHtmlToManga(mangaUrl, unparsedHtml)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get chapter list of a manga from the source
|
// Get chapter list of a manga from the source
|
||||||
public Observable<List<Chapter>> pullChaptersFromNetwork(final String mangaUrl) {
|
public Observable<List<Chapter>> pullChaptersFromNetwork(final String mangaUrl) {
|
||||||
return networkService
|
return networkService
|
||||||
.getStringResponse(getBaseUrl() + mangaUrl, requestHeaders, null)
|
.getStringResponse(getBaseUrl() + mangaUrl, requestHeaders, false)
|
||||||
.flatMap(unparsedHtml -> {
|
.flatMap(unparsedHtml -> {
|
||||||
List<Chapter> chapters = parseHtmlToChapters(unparsedHtml);
|
List<Chapter> chapters = parseHtmlToChapters(unparsedHtml);
|
||||||
return !chapters.isEmpty() ?
|
return !chapters.isEmpty() ?
|
||||||
@ -102,7 +102,7 @@ public abstract class Source extends BaseSource {
|
|||||||
|
|
||||||
public Observable<List<Page>> pullPageListFromNetwork(final String chapterUrl) {
|
public Observable<List<Page>> pullPageListFromNetwork(final String chapterUrl) {
|
||||||
return networkService
|
return networkService
|
||||||
.getStringResponse(getBaseUrl() + overrideChapterUrl(chapterUrl), requestHeaders, null)
|
.getStringResponse(getBaseUrl() + overrideChapterUrl(chapterUrl), requestHeaders, false)
|
||||||
.flatMap(unparsedHtml -> {
|
.flatMap(unparsedHtml -> {
|
||||||
List<Page> pages = convertToPages(parseHtmlToPageUrls(unparsedHtml));
|
List<Page> pages = convertToPages(parseHtmlToPageUrls(unparsedHtml));
|
||||||
return !pages.isEmpty() ?
|
return !pages.isEmpty() ?
|
||||||
@ -127,7 +127,7 @@ public abstract class Source extends BaseSource {
|
|||||||
public Observable<Page> getImageUrlFromPage(final Page page) {
|
public Observable<Page> getImageUrlFromPage(final Page page) {
|
||||||
page.setStatus(Page.LOAD_PAGE);
|
page.setStatus(Page.LOAD_PAGE);
|
||||||
return networkService
|
return networkService
|
||||||
.getStringResponse(overridePageUrl(page.getUrl()), requestHeaders, null)
|
.getStringResponse(overridePageUrl(page.getUrl()), requestHeaders, false)
|
||||||
.flatMap(unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml)))
|
.flatMap(unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml)))
|
||||||
.onErrorResumeNext(e -> {
|
.onErrorResumeNext(e -> {
|
||||||
page.setStatus(Page.ERROR);
|
page.setStatus(Page.ERROR);
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package eu.kanade.tachiyomi.data.source.model;
|
package eu.kanade.tachiyomi.data.source.model;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
||||||
import eu.kanade.tachiyomi.data.network.ProgressListener;
|
import eu.kanade.tachiyomi.data.network.ProgressListener;
|
||||||
import rx.subjects.PublishSubject;
|
import rx.subjects.PublishSubject;
|
||||||
|
|
||||||
@ -8,6 +12,7 @@ public class Page implements ProgressListener {
|
|||||||
private int pageNumber;
|
private int pageNumber;
|
||||||
private String url;
|
private String url;
|
||||||
private String imageUrl;
|
private String imageUrl;
|
||||||
|
private transient Chapter chapter;
|
||||||
private transient String imagePath;
|
private transient String imagePath;
|
||||||
private transient volatile int status;
|
private transient volatile int status;
|
||||||
private transient volatile int progress;
|
private transient volatile int progress;
|
||||||
@ -82,4 +87,16 @@ public class Page implements ProgressListener {
|
|||||||
this.statusSubject = subject;
|
this.statusSubject = subject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Chapter getChapter() {
|
||||||
|
return chapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChapter(Chapter chapter) {
|
||||||
|
this.chapter = chapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLastPage() {
|
||||||
|
List<Page> chapterPages = chapter.getPages();
|
||||||
|
return chapterPages.size() -1 == pageNumber;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.source.online.english;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.text.Html;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
@ -47,6 +48,8 @@ public class Batoto extends LoginSource {
|
|||||||
public static final String MANGA_URL = "/comic_pop?id=%s";
|
public static final String MANGA_URL = "/comic_pop?id=%s";
|
||||||
public static final String LOGIN_URL = BASE_URL + "/forums/index.php?app=core&module=global§ion=login";
|
public static final String LOGIN_URL = BASE_URL + "/forums/index.php?app=core&module=global§ion=login";
|
||||||
|
|
||||||
|
public static final Pattern staffNotice = Pattern.compile("=+Batoto Staff Notice=+([^=]+)==+", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
private Pattern datePattern;
|
private Pattern datePattern;
|
||||||
private Map<String, Integer> dateFields;
|
private Map<String, Integer> dateFields;
|
||||||
|
|
||||||
@ -204,6 +207,12 @@ public class Batoto extends LoginSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<Chapter> parseHtmlToChapters(String unparsedHtml) {
|
protected List<Chapter> parseHtmlToChapters(String unparsedHtml) {
|
||||||
|
Matcher matcher = staffNotice.matcher(unparsedHtml);
|
||||||
|
if (matcher.find()) {
|
||||||
|
String notice = Html.fromHtml(matcher.group(1)).toString().trim();
|
||||||
|
throw new RuntimeException(notice);
|
||||||
|
}
|
||||||
|
|
||||||
Document parsedDocument = Jsoup.parse(unparsedHtml);
|
Document parsedDocument = Jsoup.parse(unparsedHtml);
|
||||||
|
|
||||||
List<Chapter> chapterList = new ArrayList<>();
|
List<Chapter> chapterList = new ArrayList<>();
|
||||||
@ -234,6 +243,7 @@ public class Batoto extends LoginSource {
|
|||||||
return chapter;
|
return chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WrongConstant")
|
||||||
private long parseDateFromElement(Element dateElement) {
|
private long parseDateFromElement(Element dateElement) {
|
||||||
String dateAsString = dateElement.text();
|
String dateAsString = dateElement.text();
|
||||||
|
|
||||||
@ -249,7 +259,6 @@ public class Batoto extends LoginSource {
|
|||||||
String unit = m.group(2);
|
String unit = m.group(2);
|
||||||
|
|
||||||
Calendar cal = Calendar.getInstance();
|
Calendar cal = Calendar.getInstance();
|
||||||
// Not an error
|
|
||||||
cal.add(dateFields.get(unit), -amount);
|
cal.add(dateFields.get(unit), -amount);
|
||||||
date = cal.getTime();
|
date = cal.getTime();
|
||||||
} else {
|
} else {
|
||||||
@ -309,7 +318,7 @@ public class Batoto extends LoginSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Observable<Boolean> login(String username, String password) {
|
public Observable<Boolean> login(String username, String password) {
|
||||||
return networkService.getStringResponse(LOGIN_URL, requestHeaders, null)
|
return networkService.getStringResponse(LOGIN_URL, requestHeaders, false)
|
||||||
.flatMap(response -> doLogin(response, username, password))
|
.flatMap(response -> doLogin(response, username, password))
|
||||||
.map(this::isAuthenticationSuccessful);
|
.map(this::isAuthenticationSuccessful);
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ public class Mangafox extends Source {
|
|||||||
public static final String BASE_URL = "http://mangafox.me";
|
public static final String BASE_URL = "http://mangafox.me";
|
||||||
public static final String POPULAR_MANGAS_URL = BASE_URL + "/directory/%s";
|
public static final String POPULAR_MANGAS_URL = BASE_URL + "/directory/%s";
|
||||||
public static final String SEARCH_URL =
|
public static final String SEARCH_URL =
|
||||||
BASE_URL + "/search.php?name_method=cw&advopts=1&order=az&sort=name&name=%s&page=%s";
|
BASE_URL + "/search.php?name_method=cw&advopts=1&order=za&sort=views&name=%s&page=%s";
|
||||||
|
|
||||||
public Mangafox(Context context) {
|
public Mangafox(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
@ -28,7 +28,7 @@ public class Mangahere extends Source {
|
|||||||
public static final String NAME = "Mangahere (EN)";
|
public static final String NAME = "Mangahere (EN)";
|
||||||
public static final String BASE_URL = "http://www.mangahere.co";
|
public static final String BASE_URL = "http://www.mangahere.co";
|
||||||
public static final String POPULAR_MANGAS_URL = BASE_URL + "/directory/%s";
|
public static final String POPULAR_MANGAS_URL = BASE_URL + "/directory/%s";
|
||||||
public static final String SEARCH_URL = BASE_URL + "/search.php?name=%s&page=%s";
|
public static final String SEARCH_URL = BASE_URL + "/search.php?name=%s&page=%s&sort=views&order=za";
|
||||||
|
|
||||||
public Mangahere(Context context) {
|
public Mangahere(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
@ -111,7 +111,7 @@ public class LibraryUpdateService extends Service {
|
|||||||
.toList().toBlocking().single();
|
.toList().toBlocking().single();
|
||||||
|
|
||||||
return Observable.from(mangas)
|
return Observable.from(mangas)
|
||||||
.doOnNext(manga -> showNotification(
|
.doOnNext(manga -> showProgressNotification(
|
||||||
getString(R.string.notification_update_progress,
|
getString(R.string.notification_update_progress,
|
||||||
count.incrementAndGet(), mangas.size()), manga.title))
|
count.incrementAndGet(), mangas.size()), manga.title))
|
||||||
.concatMap(manga -> updateManga(manga)
|
.concatMap(manga -> updateManga(manga)
|
||||||
@ -123,8 +123,14 @@ public class LibraryUpdateService extends Service {
|
|||||||
.filter(pair -> pair.first > 0)
|
.filter(pair -> pair.first > 0)
|
||||||
.map(pair -> new MangaUpdate(manga, pair.first)))
|
.map(pair -> new MangaUpdate(manga, pair.first)))
|
||||||
.doOnNext(updates::add)
|
.doOnNext(updates::add)
|
||||||
.doOnCompleted(() -> showBigNotification(getString(R.string.notification_update_completed),
|
.doOnCompleted(() -> {
|
||||||
getUpdatedMangas(updates, failedUpdates)));
|
if (updates.isEmpty()) {
|
||||||
|
cancelNotification();
|
||||||
|
} else {
|
||||||
|
showResultNotification(getString(R.string.notification_update_completed),
|
||||||
|
getUpdatedMangasResult(updates, failedUpdates));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Observable<Pair<Integer, Integer>> updateManga(Manga manga) {
|
private Observable<Pair<Integer, Integer>> updateManga(Manga manga) {
|
||||||
@ -133,7 +139,7 @@ public class LibraryUpdateService extends Service {
|
|||||||
.flatMap(chapters -> db.insertOrRemoveChapters(manga, chapters));
|
.flatMap(chapters -> db.insertOrRemoveChapters(manga, chapters));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getUpdatedMangas(List<MangaUpdate> updates, List<Manga> failedUpdates) {
|
private String getUpdatedMangasResult(List<MangaUpdate> updates, List<Manga> failedUpdates) {
|
||||||
final StringBuilder result = new StringBuilder();
|
final StringBuilder result = new StringBuilder();
|
||||||
if (updates.isEmpty()) {
|
if (updates.isEmpty()) {
|
||||||
result.append(getString(R.string.notification_no_new_chapters)).append("\n");
|
result.append(getString(R.string.notification_no_new_chapters)).append("\n");
|
||||||
@ -185,7 +191,20 @@ public class LibraryUpdateService extends Service {
|
|||||||
notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build());
|
notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showBigNotification(String title, String body) {
|
private void showProgressNotification(String title, String body) {
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
|
||||||
|
.setSmallIcon(R.drawable.ic_action_refresh)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(body)
|
||||||
|
.setOngoing(true);
|
||||||
|
|
||||||
|
NotificationManager notificationManager =
|
||||||
|
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showResultNotification(String title, String body) {
|
||||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
|
||||||
.setSmallIcon(R.drawable.ic_action_refresh)
|
.setSmallIcon(R.drawable.ic_action_refresh)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
@ -199,6 +218,13 @@ public class LibraryUpdateService extends Service {
|
|||||||
notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build());
|
notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void cancelNotification() {
|
||||||
|
NotificationManager notificationManager =
|
||||||
|
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
notificationManager.cancel(UPDATE_NOTIFICATION_ID);
|
||||||
|
}
|
||||||
|
|
||||||
private PendingIntent getNotificationIntent() {
|
private PendingIntent getNotificationIntent() {
|
||||||
Intent intent = new Intent(this, MainActivity.class);
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.updater;
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.R;
|
||||||
|
import eu.kanade.tachiyomi.data.rest.GithubService;
|
||||||
|
import eu.kanade.tachiyomi.data.rest.Release;
|
||||||
|
import eu.kanade.tachiyomi.data.rest.ServiceFactory;
|
||||||
|
import eu.kanade.tachiyomi.util.ToastUtil;
|
||||||
|
import rx.Observable;
|
||||||
|
|
||||||
|
|
||||||
|
public class UpdateChecker {
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
public UpdateChecker(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns observable containing release information
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public Observable<Release> checkForApplicationUpdate() {
|
||||||
|
ToastUtil.showShort(context, context.getString(R.string.update_check_look_for_updates));
|
||||||
|
//Create Github service to retrieve Github data
|
||||||
|
GithubService service = ServiceFactory.createRetrofitService(GithubService.class, GithubService.SERVICE_ENDPOINT);
|
||||||
|
return service.getLatestVersion();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.updater;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.App;
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
|
||||||
|
|
||||||
|
public class UpdateDownloader extends AsyncTask<String, Void, Void> {
|
||||||
|
/**
|
||||||
|
* Name of cache directory.
|
||||||
|
*/
|
||||||
|
private static final String PARAMETER_CACHE_DIRECTORY = "apk_downloads";
|
||||||
|
/**
|
||||||
|
* Interface to global information about an application environment.
|
||||||
|
*/
|
||||||
|
private final Context context;
|
||||||
|
/**
|
||||||
|
* Cache directory used for cache management.
|
||||||
|
*/
|
||||||
|
private final File cacheDir;
|
||||||
|
@Inject PreferencesHelper preferencesHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor of UpdaterCache.
|
||||||
|
*
|
||||||
|
* @param context application environment interface.
|
||||||
|
*/
|
||||||
|
public UpdateDownloader(Context context) {
|
||||||
|
App.get(context).getComponent().inject(this);
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
// Get cache directory from parameter.
|
||||||
|
cacheDir = new File(preferencesHelper.getDownloadsDirectory(), PARAMETER_CACHE_DIRECTORY);
|
||||||
|
|
||||||
|
// Create cache directory.
|
||||||
|
createCacheDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create cache directory if it doesn't exist
|
||||||
|
*
|
||||||
|
* @return true if cache dir is created otherwise false.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("UnusedReturnValue")
|
||||||
|
private boolean createCacheDir() {
|
||||||
|
return !cacheDir.exists() && cacheDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(String... args) {
|
||||||
|
try {
|
||||||
|
createCacheDir();
|
||||||
|
|
||||||
|
URL url = new URL(args[0]);
|
||||||
|
HttpURLConnection c = (HttpURLConnection) url.openConnection();
|
||||||
|
c.connect();
|
||||||
|
|
||||||
|
File outputFile = new File(cacheDir, "update.apk");
|
||||||
|
if (outputFile.exists()) {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
outputFile.delete();
|
||||||
|
}
|
||||||
|
FileOutputStream fos = new FileOutputStream(outputFile);
|
||||||
|
|
||||||
|
InputStream is = c.getInputStream();
|
||||||
|
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int len1;
|
||||||
|
while ((len1 = is.read(buffer)) != -1) {
|
||||||
|
fos.write(buffer, 0, len1);
|
||||||
|
}
|
||||||
|
fos.close();
|
||||||
|
is.close();
|
||||||
|
|
||||||
|
// Prompt install interface
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
intent.setDataAndType(Uri.fromFile(outputFile), "application/vnd.android.package-archive");
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // without this flag android returned a intent error!
|
||||||
|
context.startActivity(intent);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.mangasync.services.MyAnimeList;
|
|||||||
import eu.kanade.tachiyomi.data.source.base.Source;
|
import eu.kanade.tachiyomi.data.source.base.Source;
|
||||||
import eu.kanade.tachiyomi.data.sync.LibraryUpdateService;
|
import eu.kanade.tachiyomi.data.sync.LibraryUpdateService;
|
||||||
import eu.kanade.tachiyomi.data.sync.UpdateMangaSyncService;
|
import eu.kanade.tachiyomi.data.sync.UpdateMangaSyncService;
|
||||||
|
import eu.kanade.tachiyomi.data.updater.UpdateDownloader;
|
||||||
import eu.kanade.tachiyomi.injection.module.AppModule;
|
import eu.kanade.tachiyomi.injection.module.AppModule;
|
||||||
import eu.kanade.tachiyomi.injection.module.DataModule;
|
import eu.kanade.tachiyomi.injection.module.DataModule;
|
||||||
import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter;
|
import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter;
|
||||||
@ -50,6 +51,7 @@ public interface AppComponent {
|
|||||||
void inject(ReaderActivity readerActivity);
|
void inject(ReaderActivity readerActivity);
|
||||||
void inject(MangaActivity mangaActivity);
|
void inject(MangaActivity mangaActivity);
|
||||||
void inject(SettingsAccountsFragment settingsAccountsFragment);
|
void inject(SettingsAccountsFragment settingsAccountsFragment);
|
||||||
|
|
||||||
void inject(SettingsActivity settingsActivity);
|
void inject(SettingsActivity settingsActivity);
|
||||||
|
|
||||||
void inject(Source source);
|
void inject(Source source);
|
||||||
@ -59,6 +61,8 @@ public interface AppComponent {
|
|||||||
void inject(LibraryUpdateService libraryUpdateService);
|
void inject(LibraryUpdateService libraryUpdateService);
|
||||||
void inject(DownloadService downloadService);
|
void inject(DownloadService downloadService);
|
||||||
void inject(UpdateMangaSyncService updateMangaSyncService);
|
void inject(UpdateMangaSyncService updateMangaSyncService);
|
||||||
|
|
||||||
|
void inject(UpdateDownloader updateDownloader);
|
||||||
Application application();
|
Application application();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,8 @@ import android.support.v7.app.AppCompatActivity;
|
|||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import de.greenrobot.event.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
|
||||||
import icepick.Icepick;
|
import icepick.Icepick;
|
||||||
|
|
||||||
public class BaseActivity extends AppCompatActivity {
|
public class BaseActivity extends AppCompatActivity {
|
||||||
@ -58,20 +59,8 @@ public class BaseActivity extends AppCompatActivity {
|
|||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerForStickyEvents() {
|
|
||||||
registerForStickyEvents(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerForStickyEvents(int priority) {
|
|
||||||
EventBus.getDefault().registerSticky(this, priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerForEvents() {
|
public void registerForEvents() {
|
||||||
registerForEvents(0);
|
EventBus.getDefault().register(this);
|
||||||
}
|
|
||||||
|
|
||||||
public void registerForEvents(int priority) {
|
|
||||||
EventBus.getDefault().register(this, priority);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unregisterForEvents() {
|
public void unregisterForEvents() {
|
||||||
|
@ -19,10 +19,19 @@ import android.content.Context;
|
|||||||
import android.support.design.widget.CoordinatorLayout;
|
import android.support.design.widget.CoordinatorLayout;
|
||||||
import android.support.design.widget.FloatingActionButton;
|
import android.support.design.widget.FloatingActionButton;
|
||||||
import android.support.v4.view.ViewCompat;
|
import android.support.v4.view.ViewCompat;
|
||||||
|
import android.support.v4.view.animation.FastOutSlowInInterpolator;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.animation.Animation;
|
||||||
|
import android.view.animation.AnimationUtils;
|
||||||
|
import android.view.animation.Interpolator;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.R;
|
||||||
|
|
||||||
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
|
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
|
||||||
|
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
|
||||||
|
private boolean mIsAnimatingOut = false;
|
||||||
|
|
||||||
public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
|
public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -40,12 +49,43 @@ public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
|
|||||||
final View target, final int dxConsumed, final int dyConsumed,
|
final View target, final int dxConsumed, final int dyConsumed,
|
||||||
final int dxUnconsumed, final int dyUnconsumed) {
|
final int dxUnconsumed, final int dyUnconsumed) {
|
||||||
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
|
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
|
||||||
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
|
if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) {
|
||||||
// User scrolled down and the FAB is currently visible -> hide the FAB
|
// User scrolled down and the FAB is currently visible -> hide the FAB
|
||||||
child.hide();
|
animateOut(child);
|
||||||
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
|
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
|
||||||
// User scrolled up and the FAB is currently not visible -> show the FAB
|
// User scrolled up and the FAB is currently not visible -> show the FAB
|
||||||
child.show();
|
animateIn(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same animation that FloatingActionButton.Behavior uses to hide the FAB when the AppBarLayout exits
|
||||||
|
private void animateOut(final FloatingActionButton button) {
|
||||||
|
Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_hide_to_bottom);
|
||||||
|
anim.setInterpolator(INTERPOLATOR);
|
||||||
|
anim.setDuration(200L);
|
||||||
|
anim.setAnimationListener(new Animation.AnimationListener() {
|
||||||
|
public void onAnimationStart(Animation animation) {
|
||||||
|
ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAnimationEnd(Animation animation) {
|
||||||
|
ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
|
||||||
|
button.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(final Animation animation) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
button.startAnimation(anim);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same animation that FloatingActionButton.Behavior uses to show the FAB when the AppBarLayout enters
|
||||||
|
private void animateIn(FloatingActionButton button) {
|
||||||
|
button.setVisibility(View.VISIBLE);
|
||||||
|
Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_show_from_bottom);
|
||||||
|
anim.setDuration(200L);
|
||||||
|
anim.setInterpolator(INTERPOLATOR);
|
||||||
|
button.startAnimation(anim);
|
||||||
|
}
|
||||||
}
|
}
|
@ -3,7 +3,8 @@ package eu.kanade.tachiyomi.ui.base.fragment;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
|
||||||
import de.greenrobot.event.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity;
|
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity;
|
||||||
import icepick.Icepick;
|
import icepick.Icepick;
|
||||||
|
|
||||||
@ -33,20 +34,8 @@ public class BaseFragment extends Fragment {
|
|||||||
return (BaseActivity) getActivity();
|
return (BaseActivity) getActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerForStickyEvents() {
|
|
||||||
registerForStickyEvents(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerForStickyEvents(int priority) {
|
|
||||||
EventBus.getDefault().registerSticky(this, priority);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerForEvents() {
|
public void registerForEvents() {
|
||||||
registerForEvents(0);
|
EventBus.getDefault().register(this);
|
||||||
}
|
|
||||||
|
|
||||||
public void registerForEvents(int priority) {
|
|
||||||
EventBus.getDefault().register(this, priority);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unregisterForEvents() {
|
public void unregisterForEvents() {
|
||||||
|
@ -4,7 +4,8 @@ import android.content.Context;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import de.greenrobot.event.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
|
||||||
import icepick.Icepick;
|
import icepick.Icepick;
|
||||||
import nucleus.view.ViewWithPresenter;
|
import nucleus.view.ViewWithPresenter;
|
||||||
|
|
||||||
@ -24,10 +25,6 @@ public class BasePresenter<V extends ViewWithPresenter> extends RxPresenter<V> {
|
|||||||
Icepick.saveInstanceState(this, state);
|
Icepick.saveInstanceState(this, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerForStickyEvents() {
|
|
||||||
EventBus.getDefault().registerSticky(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerForEvents() {
|
public void registerForEvents() {
|
||||||
EventBus.getDefault().register(this);
|
EventBus.getDefault().register(this);
|
||||||
}
|
}
|
||||||
|
@ -213,6 +213,137 @@ public class RxPresenter<View> extends Presenter<View> {
|
|||||||
restartableReplay(restartableId, observableFactory, onNext, null);
|
restartableReplay(restartableId, observableFactory, onNext, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A startable behaves the same as a restartable but it does not resubscribe on process restart
|
||||||
|
*
|
||||||
|
* @param startableId an id of the restartable.
|
||||||
|
* @param observableFactory a factory that should return an Observable when the startable should run.
|
||||||
|
*/
|
||||||
|
public <T> void startable(int startableId, final Func0<Observable<T>> observableFactory) {
|
||||||
|
restartables.put(startableId, () -> observableFactory.call().subscribe());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A startable behaves the same as a restartable but it does not resubscribe on process restart
|
||||||
|
*
|
||||||
|
* @param startableId an id of the restartable.
|
||||||
|
* @param observableFactory a factory that should return an Observable when the startable should run.
|
||||||
|
* @param onNext a callback that will be called when received data should be delivered to view.
|
||||||
|
* @param onError a callback that will be called if the source observable emits onError.
|
||||||
|
*/
|
||||||
|
public <T> void startable(int startableId, final Func0<Observable<T>> observableFactory,
|
||||||
|
final Action1<T> onNext, final Action1<Throwable> onError) {
|
||||||
|
|
||||||
|
restartables.put(startableId, () -> observableFactory.call().subscribe(onNext, onError));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A startable behaves the same as a restartable but it does not resubscribe on process restart
|
||||||
|
*
|
||||||
|
* @param startableId an id of the restartable.
|
||||||
|
* @param observableFactory a factory that should return an Observable when the startable should run.
|
||||||
|
* @param onNext a callback that will be called when received data should be delivered to view.
|
||||||
|
*/
|
||||||
|
public <T> void startable(int startableId, final Func0<Observable<T>> observableFactory, final Action1<T> onNext) {
|
||||||
|
restartables.put(startableId, () -> observableFactory.call().subscribe(onNext));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a shortcut that can be used instead of combining together
|
||||||
|
* {@link #startable(int, Func0)},
|
||||||
|
* {@link #deliverFirst()},
|
||||||
|
* {@link #split(Action2, Action2)}.
|
||||||
|
*
|
||||||
|
* @param startableId an id of the startable.
|
||||||
|
* @param observableFactory a factory that should return an Observable when the startable should run.
|
||||||
|
* @param onNext a callback that will be called when received data should be delivered to view.
|
||||||
|
* @param onError a callback that will be called if the source observable emits onError.
|
||||||
|
* @param <T> the type of the observable.
|
||||||
|
*/
|
||||||
|
public <T> void startableFirst(int startableId, final Func0<Observable<T>> observableFactory,
|
||||||
|
final Action2<View, T> onNext, @Nullable final Action2<View, Throwable> onError) {
|
||||||
|
|
||||||
|
restartables.put(startableId, new Func0<Subscription>() {
|
||||||
|
@Override
|
||||||
|
public Subscription call() {
|
||||||
|
return observableFactory.call()
|
||||||
|
.compose(RxPresenter.this.<T>deliverFirst())
|
||||||
|
.subscribe(split(onNext, onError));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a shortcut for calling {@link #startableFirst(int, Func0, Action2, Action2)} with the last parameter = null.
|
||||||
|
*/
|
||||||
|
public <T> void startableFirst(int startableId, final Func0<Observable<T>> observableFactory, final Action2<View, T> onNext) {
|
||||||
|
startableFirst(startableId, observableFactory, onNext, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a shortcut that can be used instead of combining together
|
||||||
|
* {@link #startable(int, Func0)},
|
||||||
|
* {@link #deliverLatestCache()},
|
||||||
|
* {@link #split(Action2, Action2)}.
|
||||||
|
*
|
||||||
|
* @param startableId an id of the startable.
|
||||||
|
* @param observableFactory a factory that should return an Observable when the startable should run.
|
||||||
|
* @param onNext a callback that will be called when received data should be delivered to view.
|
||||||
|
* @param onError a callback that will be called if the source observable emits onError.
|
||||||
|
* @param <T> the type of the observable.
|
||||||
|
*/
|
||||||
|
public <T> void startableLatestCache(int startableId, final Func0<Observable<T>> observableFactory,
|
||||||
|
final Action2<View, T> onNext, @Nullable final Action2<View, Throwable> onError) {
|
||||||
|
|
||||||
|
restartables.put(startableId, new Func0<Subscription>() {
|
||||||
|
@Override
|
||||||
|
public Subscription call() {
|
||||||
|
return observableFactory.call()
|
||||||
|
.compose(RxPresenter.this.<T>deliverLatestCache())
|
||||||
|
.subscribe(split(onNext, onError));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a shortcut for calling {@link #startableLatestCache(int, Func0, Action2, Action2)} with the last parameter = null.
|
||||||
|
*/
|
||||||
|
public <T> void startableLatestCache(int startableId, final Func0<Observable<T>> observableFactory, final Action2<View, T> onNext) {
|
||||||
|
startableLatestCache(startableId, observableFactory, onNext, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a shortcut that can be used instead of combining together
|
||||||
|
* {@link #startable(int, Func0)},
|
||||||
|
* {@link #deliverReplay()},
|
||||||
|
* {@link #split(Action2, Action2)}.
|
||||||
|
*
|
||||||
|
* @param startableId an id of the startable.
|
||||||
|
* @param observableFactory a factory that should return an Observable when the startable should run.
|
||||||
|
* @param onNext a callback that will be called when received data should be delivered to view.
|
||||||
|
* @param onError a callback that will be called if the source observable emits onError.
|
||||||
|
* @param <T> the type of the observable.
|
||||||
|
*/
|
||||||
|
public <T> void startableReplay(int startableId, final Func0<Observable<T>> observableFactory,
|
||||||
|
final Action2<View, T> onNext, @Nullable final Action2<View, Throwable> onError) {
|
||||||
|
|
||||||
|
restartables.put(startableId, new Func0<Subscription>() {
|
||||||
|
@Override
|
||||||
|
public Subscription call() {
|
||||||
|
return observableFactory.call()
|
||||||
|
.compose(RxPresenter.this.<T>deliverReplay())
|
||||||
|
.subscribe(split(onNext, onError));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a shortcut for calling {@link #startableReplay(int, Func0, Action2, Action2)} with the last parameter = null.
|
||||||
|
*/
|
||||||
|
public <T> void startableReplay(int startableId, final Func0<Observable<T>> observableFactory, final Action2<View, T> onNext) {
|
||||||
|
startableReplay(startableId, observableFactory, onNext, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an {@link rx.Observable.Transformer} that couples views with data that has been emitted by
|
* Returns an {@link rx.Observable.Transformer} that couples views with data that has been emitted by
|
||||||
* the source {@link rx.Observable}.
|
* the source {@link rx.Observable}.
|
||||||
|
@ -20,6 +20,7 @@ import android.view.ViewGroup;
|
|||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.ViewSwitcher;
|
import android.widget.ViewSwitcher;
|
||||||
@ -66,7 +67,7 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter>
|
|||||||
private EndlessListScrollListener listScrollListener;
|
private EndlessListScrollListener listScrollListener;
|
||||||
|
|
||||||
@State String query = "";
|
@State String query = "";
|
||||||
@State int selectedIndex = -1;
|
@State int selectedIndex;
|
||||||
private final int SEARCH_TIMEOUT = 1000;
|
private final int SEARCH_TIMEOUT = 1000;
|
||||||
|
|
||||||
private PublishSubject<String> queryDebouncerSubject;
|
private PublishSubject<String> queryDebouncerSubject;
|
||||||
@ -122,25 +123,27 @@ public class CatalogueFragment extends BaseRxFragment<CataloguePresenter>
|
|||||||
Context themedContext = getBaseActivity().getSupportActionBar() != null ?
|
Context themedContext = getBaseActivity().getSupportActionBar() != null ?
|
||||||
getBaseActivity().getSupportActionBar().getThemedContext() : getActivity();
|
getBaseActivity().getSupportActionBar().getThemedContext() : getActivity();
|
||||||
spinner = new Spinner(themedContext);
|
spinner = new Spinner(themedContext);
|
||||||
CatalogueSpinnerAdapter spinnerAdapter = new CatalogueSpinnerAdapter(themedContext,
|
ArrayAdapter<Source> spinnerAdapter = new ArrayAdapter<>(themedContext,
|
||||||
android.R.layout.simple_spinner_item, getPresenter().getEnabledSources());
|
android.R.layout.simple_spinner_item, getPresenter().getEnabledSources());
|
||||||
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
if (savedState == null) selectedIndex = spinnerAdapter.getEmptyIndex();
|
|
||||||
|
if (savedState == null) {
|
||||||
|
selectedIndex = getPresenter().getLastUsedSourceIndex();
|
||||||
|
}
|
||||||
spinner.setAdapter(spinnerAdapter);
|
spinner.setAdapter(spinnerAdapter);
|
||||||
spinner.setSelection(selectedIndex);
|
spinner.setSelection(selectedIndex);
|
||||||
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
Source source = spinnerAdapter.getItem(position);
|
Source source = spinnerAdapter.getItem(position);
|
||||||
// We add an empty source with id -1 that acts as a placeholder to show a hint
|
if (selectedIndex != position || adapter.isEmpty()) {
|
||||||
// that asks to select a source
|
|
||||||
if (source.getId() != -1 && (selectedIndex != position || adapter.isEmpty())) {
|
|
||||||
// Set previous selection if it's not a valid source and notify the user
|
// Set previous selection if it's not a valid source and notify the user
|
||||||
if (!getPresenter().isValidSource(source)) {
|
if (!getPresenter().isValidSource(source)) {
|
||||||
spinner.setSelection(spinnerAdapter.getEmptyIndex());
|
spinner.setSelection(getPresenter().findFirstValidSource());
|
||||||
ToastUtil.showShort(getActivity(), R.string.source_requires_login);
|
ToastUtil.showShort(getActivity(), R.string.source_requires_login);
|
||||||
} else {
|
} else {
|
||||||
selectedIndex = position;
|
selectedIndex = position;
|
||||||
|
getPresenter().setEnabledSource(selectedIndex);
|
||||||
showProgressBar();
|
showProgressBar();
|
||||||
glm.scrollToPositionWithOffset(0, 0);
|
glm.scrollToPositionWithOffset(0, 0);
|
||||||
llm.scrollToPositionWithOffset(0, 0);
|
llm.scrollToPositionWithOffset(0, 0);
|
||||||
|
@ -4,6 +4,8 @@ import android.view.View;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.mikepenz.iconics.view.IconicsImageView;
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import eu.kanade.tachiyomi.R;
|
import eu.kanade.tachiyomi.R;
|
||||||
@ -13,7 +15,7 @@ public class CatalogueGridHolder extends CatalogueHolder {
|
|||||||
|
|
||||||
@Bind(R.id.title) TextView title;
|
@Bind(R.id.title) TextView title;
|
||||||
@Bind(R.id.thumbnail) ImageView thumbnail;
|
@Bind(R.id.thumbnail) ImageView thumbnail;
|
||||||
@Bind(R.id.favorite_sticker) ImageView favoriteSticker;
|
@Bind(R.id.favorite_sticker) IconicsImageView favoriteSticker;
|
||||||
|
|
||||||
public CatalogueGridHolder(View view, CatalogueAdapter adapter, OnListItemClickListener listener) {
|
public CatalogueGridHolder(View view, CatalogueAdapter adapter, OnListItemClickListener listener) {
|
||||||
super(view, adapter, listener);
|
super(view, adapter, listener);
|
||||||
@ -23,7 +25,10 @@ public class CatalogueGridHolder extends CatalogueHolder {
|
|||||||
@Override
|
@Override
|
||||||
public void onSetValues(Manga manga, CataloguePresenter presenter) {
|
public void onSetValues(Manga manga, CataloguePresenter presenter) {
|
||||||
title.setText(manga.title);
|
title.setText(manga.title);
|
||||||
|
// Set visibility of in library icon.
|
||||||
favoriteSticker.setVisibility(manga.favorite ? View.VISIBLE : View.GONE);
|
favoriteSticker.setVisibility(manga.favorite ? View.VISIBLE : View.GONE);
|
||||||
|
// Set alpha of thumbnail.
|
||||||
|
thumbnail.setAlpha(manga.favorite ? 0.3f : 1.0f);
|
||||||
setImage(manga, presenter);
|
setImage(manga, presenter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> {
|
|||||||
@Inject CoverCache coverCache;
|
@Inject CoverCache coverCache;
|
||||||
@Inject PreferencesHelper prefs;
|
@Inject PreferencesHelper prefs;
|
||||||
|
|
||||||
|
private List<Source> sources;
|
||||||
private Source source;
|
private Source source;
|
||||||
@State int sourceId;
|
@State int sourceId;
|
||||||
|
|
||||||
@ -53,23 +54,25 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> {
|
|||||||
super.onCreate(savedState);
|
super.onCreate(savedState);
|
||||||
|
|
||||||
if (savedState != null) {
|
if (savedState != null) {
|
||||||
onProcessRestart();
|
source = sourceManager.get(sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sources = sourceManager.getSources();
|
||||||
|
|
||||||
mangaDetailSubject = PublishSubject.create();
|
mangaDetailSubject = PublishSubject.create();
|
||||||
|
|
||||||
pager = new RxPager<>();
|
pager = new RxPager<>();
|
||||||
|
|
||||||
restartableReplay(GET_MANGA_LIST,
|
startableReplay(GET_MANGA_LIST,
|
||||||
pager::results,
|
pager::results,
|
||||||
(view, pair) -> view.onAddPage(pair.first, pair.second));
|
(view, pair) -> view.onAddPage(pair.first, pair.second));
|
||||||
|
|
||||||
restartableFirst(GET_MANGA_PAGE,
|
startableFirst(GET_MANGA_PAGE,
|
||||||
() -> pager.request(page -> getMangasPageObservable(page + 1)),
|
() -> pager.request(page -> getMangasPageObservable(page + 1)),
|
||||||
(view, next) -> {},
|
(view, next) -> {},
|
||||||
(view, error) -> view.onAddPageError());
|
(view, error) -> view.onAddPageError());
|
||||||
|
|
||||||
restartableLatestCache(GET_MANGA_DETAIL,
|
startableLatestCache(GET_MANGA_DETAIL,
|
||||||
() -> mangaDetailSubject
|
() -> mangaDetailSubject
|
||||||
.observeOn(Schedulers.io())
|
.observeOn(Schedulers.io())
|
||||||
.flatMap(Observable::from)
|
.flatMap(Observable::from)
|
||||||
@ -84,13 +87,6 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> {
|
|||||||
.subscribe(this::setDisplayMode));
|
.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) {
|
private void setDisplayMode(boolean asList) {
|
||||||
this.isListMode = asList;
|
this.isListMode = asList;
|
||||||
if (asList) {
|
if (asList) {
|
||||||
@ -175,6 +171,14 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> {
|
|||||||
return lastMangasPage != null && lastMangasPage.nextPageUrl != null;
|
return lastMangasPage != null && lastMangasPage.nextPageUrl != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getLastUsedSourceIndex() {
|
||||||
|
int index = prefs.lastUsedCatalogueSource().get();
|
||||||
|
if (index < 0 || index >= sources.size() || !isValidSource(sources.get(index))) {
|
||||||
|
return findFirstValidSource();
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isValidSource(Source source) {
|
public boolean isValidSource(Source source) {
|
||||||
if (!source.isLoginRequired() || source.isLogged())
|
if (!source.isLoginRequired() || source.isLogged())
|
||||||
return true;
|
return true;
|
||||||
@ -183,6 +187,19 @@ public class CataloguePresenter extends BasePresenter<CatalogueFragment> {
|
|||||||
|| prefs.getSourcePassword(source).equals(""));
|
|| prefs.getSourcePassword(source).equals(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int findFirstValidSource() {
|
||||||
|
for (int i = 0; i < sources.size(); i++) {
|
||||||
|
if (isValidSource(sources.get(i))) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnabledSource(int index) {
|
||||||
|
prefs.lastUsedCatalogueSource().set(index);
|
||||||
|
}
|
||||||
|
|
||||||
public List<Source> getEnabledSources() {
|
public List<Source> getEnabledSources() {
|
||||||
// TODO filter by enabled source
|
// TODO filter by enabled source
|
||||||
return sourceManager.getSources();
|
return sourceManager.getSources();
|
||||||
|
@ -1,120 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.catalogue;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import org.jsoup.nodes.Document;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
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.source.base.Source;
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.MangasPage;
|
|
||||||
|
|
||||||
public class CatalogueSpinnerAdapter extends ArrayAdapter<Source> {
|
|
||||||
|
|
||||||
public CatalogueSpinnerAdapter(Context context, int resource, List<Source> sources) {
|
|
||||||
super(context, resource, sources);
|
|
||||||
sources.add(new SimpleSource());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
|
||||||
|
|
||||||
View v = super.getView(position, convertView, parent);
|
|
||||||
if (position == getCount()) {
|
|
||||||
((TextView)v.findViewById(android.R.id.text1)).setText("");
|
|
||||||
((TextView)v.findViewById(android.R.id.text1)).setHint(getItem(getCount()).getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
return super.getCount()-1; // you dont display last item. It is used as hint.
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getEmptyIndex() {
|
|
||||||
return getCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SimpleSource extends Source {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return getContext().getString(R.string.select_source);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getId() {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getBaseUrl() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLoginRequired() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getInitialPopularMangasUrl() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getInitialSearchUrl(String query) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<Manga> parsePopularMangasFromHtml(Document parsedHtml) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String parseNextPopularMangasUrl(Document parsedHtml, MangasPage page) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<Manga> parseSearchFromHtml(Document parsedHtml) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String parseNextSearchUrl(Document parsedHtml, MangasPage page, String query) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Manga parseHtmlToManga(String mangaUrl, String unparsedHtml) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<Chapter> parseHtmlToChapters(String unparsedHtml) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<String> parseHtmlToPageUrls(String unparsedHtml) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String parseHtmlToImageUrl(String unparsedHtml) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -29,7 +29,8 @@ public class DownloadFragment extends BaseRxFragment<DownloadPresenter> {
|
|||||||
private DownloadAdapter adapter;
|
private DownloadAdapter adapter;
|
||||||
|
|
||||||
private MenuItem startButton;
|
private MenuItem startButton;
|
||||||
private MenuItem stopButton;
|
private MenuItem pauseButton;
|
||||||
|
private MenuItem clearButton;
|
||||||
|
|
||||||
private Subscription queueStatusSubscription;
|
private Subscription queueStatusSubscription;
|
||||||
private boolean isRunning;
|
private boolean isRunning;
|
||||||
@ -64,11 +65,16 @@ public class DownloadFragment extends BaseRxFragment<DownloadPresenter> {
|
|||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.download_queue, menu);
|
inflater.inflate(R.menu.download_queue, menu);
|
||||||
startButton = menu.findItem(R.id.start_queue);
|
startButton = menu.findItem(R.id.start_queue);
|
||||||
stopButton = menu.findItem(R.id.stop_queue);
|
pauseButton = menu.findItem(R.id.pause_queue);
|
||||||
|
clearButton = menu.findItem(R.id.clear_queue);
|
||||||
|
|
||||||
|
if(adapter.getItemCount() > 0) {
|
||||||
|
clearButton.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
// Menu seems to be inflated after onResume in fragments, so we initialize them here
|
// Menu seems to be inflated after onResume in fragments, so we initialize them here
|
||||||
startButton.setVisible(!isRunning && !getPresenter().downloadManager.getQueue().isEmpty());
|
startButton.setVisible(!isRunning && !getPresenter().downloadManager.getQueue().isEmpty());
|
||||||
stopButton.setVisible(isRunning);
|
pauseButton.setVisible(isRunning);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -77,9 +83,14 @@ public class DownloadFragment extends BaseRxFragment<DownloadPresenter> {
|
|||||||
case R.id.start_queue:
|
case R.id.start_queue:
|
||||||
DownloadService.start(getActivity());
|
DownloadService.start(getActivity());
|
||||||
break;
|
break;
|
||||||
case R.id.stop_queue:
|
case R.id.pause_queue:
|
||||||
DownloadService.stop(getActivity());
|
DownloadService.stop(getActivity());
|
||||||
break;
|
break;
|
||||||
|
case R.id.clear_queue:
|
||||||
|
DownloadService.stop(getActivity());
|
||||||
|
getPresenter().clearQueue();
|
||||||
|
clearButton.setVisible(false);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
@ -101,8 +112,8 @@ public class DownloadFragment extends BaseRxFragment<DownloadPresenter> {
|
|||||||
isRunning = running;
|
isRunning = running;
|
||||||
if (startButton != null)
|
if (startButton != null)
|
||||||
startButton.setVisible(!running && !getPresenter().downloadManager.getQueue().isEmpty());
|
startButton.setVisible(!running && !getPresenter().downloadManager.getQueue().isEmpty());
|
||||||
if (stopButton != null)
|
if (pauseButton != null)
|
||||||
stopButton.setVisible(running);
|
pauseButton.setVisible(running);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createAdapter() {
|
private void createAdapter() {
|
||||||
|
@ -20,15 +20,13 @@ import timber.log.Timber;
|
|||||||
|
|
||||||
public class DownloadPresenter extends BasePresenter<DownloadFragment> {
|
public class DownloadPresenter extends BasePresenter<DownloadFragment> {
|
||||||
|
|
||||||
|
public final static int GET_DOWNLOAD_QUEUE = 1;
|
||||||
@Inject DownloadManager downloadManager;
|
@Inject DownloadManager downloadManager;
|
||||||
|
|
||||||
private DownloadQueue downloadQueue;
|
private DownloadQueue downloadQueue;
|
||||||
private Subscription statusSubscription;
|
private Subscription statusSubscription;
|
||||||
private Subscription pageProgressSubscription;
|
private Subscription pageProgressSubscription;
|
||||||
private HashMap<Download, Subscription> progressSubscriptions;
|
private HashMap<Download, Subscription> progressSubscriptions;
|
||||||
|
|
||||||
public final static int GET_DOWNLOAD_QUEUE = 1;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedState) {
|
protected void onCreate(Bundle savedState) {
|
||||||
super.onCreate(savedState);
|
super.onCreate(savedState);
|
||||||
@ -57,6 +55,7 @@ public class DownloadPresenter extends BasePresenter<DownloadFragment> {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
add(pageProgressSubscription = downloadQueue.getProgressObservable()
|
add(pageProgressSubscription = downloadQueue.getProgressObservable()
|
||||||
|
.onBackpressureBuffer()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(view::updateDownloadedPages));
|
.subscribe(view::updateDownloadedPages));
|
||||||
}
|
}
|
||||||
@ -122,4 +121,8 @@ public class DownloadPresenter extends BasePresenter<DownloadFragment> {
|
|||||||
remove(statusSubscription);
|
remove(statusSubscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearQueue() {
|
||||||
|
downloadQueue.clear();
|
||||||
|
start(GET_DOWNLOAD_QUEUE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,6 @@ package eu.kanade.tachiyomi.ui.library;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Filter;
|
|
||||||
import android.widget.Filterable;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -12,28 +10,24 @@ import java.util.List;
|
|||||||
import eu.davidea.flexibleadapter.FlexibleAdapter;
|
import eu.davidea.flexibleadapter.FlexibleAdapter;
|
||||||
import eu.kanade.tachiyomi.R;
|
import eu.kanade.tachiyomi.R;
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
import eu.kanade.tachiyomi.data.database.models.Manga;
|
||||||
import rx.Observable;
|
|
||||||
|
|
||||||
public class LibraryCategoryAdapter extends FlexibleAdapter<LibraryHolder, Manga>
|
public class LibraryCategoryAdapter extends FlexibleAdapter<LibraryHolder, Manga> {
|
||||||
implements Filterable {
|
|
||||||
|
|
||||||
List<Manga> mangas;
|
private List<Manga> mangas;
|
||||||
Filter filter;
|
|
||||||
private LibraryCategoryFragment fragment;
|
private LibraryCategoryFragment fragment;
|
||||||
|
|
||||||
public LibraryCategoryAdapter(LibraryCategoryFragment fragment) {
|
public LibraryCategoryAdapter(LibraryCategoryFragment fragment) {
|
||||||
this.fragment = fragment;
|
this.fragment = fragment;
|
||||||
mItems = new ArrayList<>();
|
mItems = new ArrayList<>();
|
||||||
filter = new LibraryFilter();
|
|
||||||
setHasStableIds(true);
|
setHasStableIds(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setItems(List<Manga> list) {
|
public void setItems(List<Manga> list) {
|
||||||
mItems = list;
|
mItems = list;
|
||||||
notifyDataSetChanged();
|
|
||||||
|
|
||||||
// TODO needed for filtering?
|
// A copy of manga that it's always unfiltered
|
||||||
mangas = list;
|
mangas = new ArrayList<>(list);
|
||||||
|
updateDataSet(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
@ -47,7 +41,16 @@ public class LibraryCategoryAdapter extends FlexibleAdapter<LibraryHolder, Manga
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateDataSet(String param) {
|
public void updateDataSet(String param) {
|
||||||
|
if (mangas != null) {
|
||||||
|
filterItems(mangas);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean filterObject(Manga manga, String query) {
|
||||||
|
return (manga.title != null && manga.title.toLowerCase().contains(query)) ||
|
||||||
|
(manga.author != null && manga.author.toLowerCase().contains(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -61,7 +64,6 @@ public class LibraryCategoryAdapter extends FlexibleAdapter<LibraryHolder, Manga
|
|||||||
final LibraryPresenter presenter = ((LibraryFragment) fragment.getParentFragment()).getPresenter();
|
final LibraryPresenter presenter = ((LibraryFragment) fragment.getParentFragment()).getPresenter();
|
||||||
final Manga manga = getItem(position);
|
final Manga manga = getItem(position);
|
||||||
holder.onSetValues(manga, presenter);
|
holder.onSetValues(manga, presenter);
|
||||||
|
|
||||||
//When user scrolls this bind the correct selection status
|
//When user scrolls this bind the correct selection status
|
||||||
holder.itemView.setActivated(isSelected(position));
|
holder.itemView.setActivated(isSelected(position));
|
||||||
}
|
}
|
||||||
@ -70,40 +72,4 @@ public class LibraryCategoryAdapter extends FlexibleAdapter<LibraryHolder, Manga
|
|||||||
return fragment.recycler.getItemWidth() / 3 * 4;
|
return fragment.recycler.getItemWidth() / 3 * 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Filter getFilter() {
|
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LibraryFilter extends Filter {
|
|
||||||
@Override
|
|
||||||
protected FilterResults performFiltering(CharSequence charSequence) {
|
|
||||||
FilterResults results = new FilterResults();
|
|
||||||
String query = charSequence.toString().toLowerCase();
|
|
||||||
|
|
||||||
if (query.length() == 0) {
|
|
||||||
results.values = mangas;
|
|
||||||
results.count = mangas.size();
|
|
||||||
} else {
|
|
||||||
List<Manga> filteredMangas = Observable.from(mangas)
|
|
||||||
.filter(x ->
|
|
||||||
(x.title != null && x.title.toLowerCase().contains(query)) ||
|
|
||||||
(x.author != null && x.author.toLowerCase().contains(query)) ||
|
|
||||||
(x.artist != null && x.artist.toLowerCase().contains(query)))
|
|
||||||
.toList()
|
|
||||||
.toBlocking()
|
|
||||||
.single();
|
|
||||||
results.values = filteredMangas;
|
|
||||||
results.count = filteredMangas.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void publishResults(CharSequence constraint, FilterResults results) {
|
|
||||||
setItems((List<Manga>) results.values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,9 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import com.f2prateek.rx.preferences.Preference;
|
import com.f2prateek.rx.preferences.Preference;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -22,7 +25,6 @@ import eu.kanade.tachiyomi.event.LibraryMangasEvent;
|
|||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment;
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment;
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaActivity;
|
import eu.kanade.tachiyomi.ui.manga.MangaActivity;
|
||||||
import eu.kanade.tachiyomi.util.EventBusHook;
|
|
||||||
import eu.kanade.tachiyomi.widget.AutofitRecyclerView;
|
import eu.kanade.tachiyomi.widget.AutofitRecyclerView;
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import rx.Subscription;
|
import rx.Subscription;
|
||||||
@ -37,6 +39,7 @@ public class LibraryCategoryFragment extends BaseFragment
|
|||||||
private List<Manga> mangas;
|
private List<Manga> mangas;
|
||||||
|
|
||||||
private Subscription numColumnsSubscription;
|
private Subscription numColumnsSubscription;
|
||||||
|
private Subscription searchSubscription;
|
||||||
|
|
||||||
public static LibraryCategoryFragment newInstance(int position) {
|
public static LibraryCategoryFragment newInstance(int position) {
|
||||||
LibraryCategoryFragment fragment = new LibraryCategoryFragment();
|
LibraryCategoryFragment fragment = new LibraryCategoryFragment();
|
||||||
@ -44,6 +47,8 @@ public class LibraryCategoryFragment extends BaseFragment
|
|||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
|
||||||
// Inflate the layout for this fragment
|
// Inflate the layout for this fragment
|
||||||
@ -77,19 +82,29 @@ public class LibraryCategoryFragment extends BaseFragment
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchSubscription = getLibraryPresenter().searchSubject
|
||||||
|
.subscribe(text -> {
|
||||||
|
adapter.setSearchText(text);
|
||||||
|
adapter.updateDataSet();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
numColumnsSubscription.unsubscribe();
|
numColumnsSubscription.unsubscribe();
|
||||||
|
searchSubscription.unsubscribe();
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
registerForStickyEvents();
|
registerForEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -104,8 +119,8 @@ public class LibraryCategoryFragment extends BaseFragment
|
|||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventBusHook
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
public void onEventMainThread(LibraryMangasEvent event) {
|
public void onEvent(LibraryMangasEvent event) {
|
||||||
List<Category> categories = getLibraryFragment().getAdapter().categories;
|
List<Category> categories = getLibraryFragment().getAdapter().categories;
|
||||||
// When a category is deleted, the index can be greater than the number of categories
|
// When a category is deleted, the index can be greater than the number of categories
|
||||||
if (position >= categories.size())
|
if (position >= categories.size())
|
||||||
@ -155,15 +170,16 @@ public class LibraryCategoryFragment extends BaseFragment
|
|||||||
|
|
||||||
private void toggleSelection(int position) {
|
private void toggleSelection(int position) {
|
||||||
LibraryFragment f = getLibraryFragment();
|
LibraryFragment f = getLibraryFragment();
|
||||||
|
|
||||||
adapter.toggleSelection(position, false);
|
adapter.toggleSelection(position, false);
|
||||||
f.getPresenter().setSelection(adapter.getItem(position), adapter.isSelected(position));
|
f.getPresenter().setSelection(adapter.getItem(position), adapter.isSelected(position));
|
||||||
|
|
||||||
int count = f.getPresenter().selectedMangas.size();
|
int count = f.getPresenter().selectedMangas.size();
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
f.destroyActionModeIfNeeded();
|
f.destroyActionModeIfNeeded();
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
f.setContextTitle(count);
|
f.setContextTitle(count);
|
||||||
|
f.setVisibilityOfCoverEdit(count);
|
||||||
f.invalidateActionMode();
|
f.invalidateActionMode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package eu.kanade.tachiyomi.ui.library;
|
package eu.kanade.tachiyomi.ui.library;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.design.widget.AppBarLayout;
|
import android.support.design.widget.AppBarLayout;
|
||||||
import android.support.design.widget.TabLayout;
|
import android.support.design.widget.TabLayout;
|
||||||
import android.support.v4.view.ViewPager;
|
import android.support.v4.view.ViewPager;
|
||||||
import android.support.v7.view.ActionMode;
|
import android.support.v7.view.ActionMode;
|
||||||
|
import android.support.v7.widget.SearchView;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@ -16,22 +20,27 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import com.afollestad.materialdialogs.MaterialDialog;
|
import com.afollestad.materialdialogs.MaterialDialog;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import de.greenrobot.event.EventBus;
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter;
|
import eu.davidea.flexibleadapter.FlexibleAdapter;
|
||||||
import eu.kanade.tachiyomi.R;
|
import eu.kanade.tachiyomi.R;
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category;
|
import eu.kanade.tachiyomi.data.database.models.Category;
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
import eu.kanade.tachiyomi.data.database.models.Manga;
|
||||||
|
import eu.kanade.tachiyomi.data.io.IOHandler;
|
||||||
import eu.kanade.tachiyomi.data.sync.LibraryUpdateService;
|
import eu.kanade.tachiyomi.data.sync.LibraryUpdateService;
|
||||||
import eu.kanade.tachiyomi.event.LibraryMangasEvent;
|
import eu.kanade.tachiyomi.event.LibraryMangasEvent;
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
|
||||||
import eu.kanade.tachiyomi.ui.library.category.CategoryActivity;
|
import eu.kanade.tachiyomi.ui.library.category.CategoryActivity;
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity;
|
import eu.kanade.tachiyomi.ui.main.MainActivity;
|
||||||
|
import eu.kanade.tachiyomi.util.ToastUtil;
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import nucleus.factory.RequiresPresenter;
|
import nucleus.factory.RequiresPresenter;
|
||||||
|
|
||||||
@ -39,16 +48,25 @@ import nucleus.factory.RequiresPresenter;
|
|||||||
public class LibraryFragment extends BaseRxFragment<LibraryPresenter>
|
public class LibraryFragment extends BaseRxFragment<LibraryPresenter>
|
||||||
implements ActionMode.Callback {
|
implements ActionMode.Callback {
|
||||||
|
|
||||||
@Bind(R.id.view_pager) ViewPager viewPager;
|
|
||||||
private TabLayout tabs;
|
private static final int REQUEST_IMAGE_OPEN = 101;
|
||||||
private AppBarLayout appBar;
|
|
||||||
|
|
||||||
protected LibraryAdapter adapter;
|
protected LibraryAdapter adapter;
|
||||||
|
|
||||||
private ActionMode actionMode;
|
@Bind(R.id.view_pager) ViewPager viewPager;
|
||||||
|
|
||||||
@State int activeCategory;
|
@State int activeCategory;
|
||||||
|
|
||||||
|
@State String query = "";
|
||||||
|
|
||||||
|
private TabLayout tabs;
|
||||||
|
|
||||||
|
private AppBarLayout appBar;
|
||||||
|
|
||||||
|
private ActionMode actionMode;
|
||||||
|
|
||||||
|
private Manga selectedCoverManga;
|
||||||
|
|
||||||
public static LibraryFragment newInstance() {
|
public static LibraryFragment newInstance() {
|
||||||
return new LibraryFragment();
|
return new LibraryFragment();
|
||||||
}
|
}
|
||||||
@ -60,8 +78,7 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
|
||||||
Bundle savedInstanceState) {
|
|
||||||
// Inflate the layout for this fragment
|
// Inflate the layout for this fragment
|
||||||
View view = inflater.inflate(R.layout.fragment_library, container, false);
|
View view = inflater.inflate(R.layout.fragment_library, container, false);
|
||||||
setToolbarTitle(getString(R.string.label_library));
|
setToolbarTitle(getString(R.string.label_library));
|
||||||
@ -75,6 +92,10 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter>
|
|||||||
viewPager.setAdapter(adapter);
|
viewPager.setAdapter(adapter);
|
||||||
tabs.setupWithViewPager(viewPager);
|
tabs.setupWithViewPager(viewPager);
|
||||||
|
|
||||||
|
if (savedState != null) {
|
||||||
|
getPresenter().searchSubject.onNext(query);
|
||||||
|
}
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,12 +105,6 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter>
|
|||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPause() {
|
|
||||||
EventBus.getDefault().removeStickyEvent(LibraryMangasEvent.class);
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle bundle) {
|
public void onSaveInstanceState(Bundle bundle) {
|
||||||
activeCategory = viewPager.getCurrentItem();
|
activeCategory = viewPager.getCurrentItem();
|
||||||
@ -99,6 +114,29 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter>
|
|||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.library, menu);
|
inflater.inflate(R.menu.library, menu);
|
||||||
|
|
||||||
|
// Initialize search menu
|
||||||
|
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||||
|
final SearchView searchView = (SearchView) searchItem.getActionView();
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(query)) {
|
||||||
|
searchItem.expandActionView();
|
||||||
|
searchView.setQuery(query, true);
|
||||||
|
searchView.clearFocus();
|
||||||
|
}
|
||||||
|
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onQueryTextSubmit(String query) {
|
||||||
|
onSearchTextChange(query);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onQueryTextChange(String newText) {
|
||||||
|
onSearchTextChange(newText);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -115,6 +153,11 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter>
|
|||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onSearchTextChange(String query) {
|
||||||
|
this.query = query;
|
||||||
|
getPresenter().searchSubject.onNext(query);
|
||||||
|
}
|
||||||
|
|
||||||
private void onEditCategories() {
|
private void onEditCategories() {
|
||||||
Intent intent = CategoryActivity.newIntent(getActivity());
|
Intent intent = CategoryActivity.newIntent(getActivity());
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
@ -158,6 +201,11 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter>
|
|||||||
actionMode.setTitle(getString(R.string.label_selected, count));
|
actionMode.setTitle(getString(R.string.label_selected, count));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setVisibilityOfCoverEdit(int count) {
|
||||||
|
// If count = 1 display edit button
|
||||||
|
actionMode.getMenu().findItem(R.id.action_edit_cover).setVisible((count == 1));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||||
mode.getMenuInflater().inflate(R.menu.library_selection, menu);
|
mode.getMenuInflater().inflate(R.menu.library_selection, menu);
|
||||||
@ -173,6 +221,11 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter>
|
|||||||
@Override
|
@Override
|
||||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
|
case R.id.action_edit_cover:
|
||||||
|
changeSelectedCover(getPresenter().selectedMangas);
|
||||||
|
rebuildAdapter();
|
||||||
|
destroyActionModeIfNeeded();
|
||||||
|
return true;
|
||||||
case R.id.action_move_to_category:
|
case R.id.action_move_to_category:
|
||||||
moveMangasToCategories(getPresenter().selectedMangas);
|
moveMangasToCategories(getPresenter().selectedMangas);
|
||||||
return true;
|
return true;
|
||||||
@ -184,6 +237,15 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO workaround. Covers won't refresh any other way.
|
||||||
|
*/
|
||||||
|
public void rebuildAdapter() {
|
||||||
|
adapter = new LibraryAdapter(getChildFragmentManager());
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
tabs.setupWithViewPager(viewPager);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyActionMode(ActionMode mode) {
|
public void onDestroyActionMode(ActionMode mode) {
|
||||||
adapter.setSelectionMode(FlexibleAdapter.MODE_SINGLE);
|
adapter.setSelectionMode(FlexibleAdapter.MODE_SINGLE);
|
||||||
@ -197,6 +259,53 @@ public class LibraryFragment extends BaseRxFragment<LibraryPresenter>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void changeSelectedCover(List<Manga> mangas) {
|
||||||
|
if (mangas.size() == 1) {
|
||||||
|
selectedCoverManga = mangas.get(0);
|
||||||
|
if (selectedCoverManga.favorite) {
|
||||||
|
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setType("image/*");
|
||||||
|
intent.setAction(Intent.ACTION_GET_CONTENT);
|
||||||
|
startActivityForResult(Intent.createChooser(intent,
|
||||||
|
getString(R.string.file_select_cover)), REQUEST_IMAGE_OPEN);
|
||||||
|
} else {
|
||||||
|
ToastUtil.showShort(getContext(), R.string.notification_first_add_to_library);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
switch (requestCode) {
|
||||||
|
case (REQUEST_IMAGE_OPEN):
|
||||||
|
if (selectedCoverManga != null) {
|
||||||
|
// Get the file's content URI from the incoming Intent
|
||||||
|
Uri selectedImageUri = data.getData();
|
||||||
|
|
||||||
|
// Convert to absolute path to prevent FileNotFoundException
|
||||||
|
String result = IOHandler.getFilePath(selectedImageUri,
|
||||||
|
getContext().getContentResolver(), getContext());
|
||||||
|
|
||||||
|
// Get file from filepath
|
||||||
|
File picture = new File(result != null ? result : "");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Update cover to selected file, show error if something went wrong
|
||||||
|
if (!getPresenter().editCoverWithLocalFile(picture, selectedCoverManga))
|
||||||
|
ToastUtil.showShort(getContext(), R.string.notification_manga_update_failed);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void moveMangasToCategories(List<Manga> mangas) {
|
private void moveMangasToCategories(List<Manga> mangas) {
|
||||||
new MaterialDialog.Builder(getActivity())
|
new MaterialDialog.Builder(getActivity())
|
||||||
.title(R.string.action_move_category)
|
.title(R.string.action_move_category)
|
||||||
|
@ -50,4 +50,6 @@ public class LibraryHolder extends FlexibleViewHolder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,16 @@ package eu.kanade.tachiyomi.ui.library;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import de.greenrobot.event.EventBus;
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache;
|
import eu.kanade.tachiyomi.data.cache.CoverCache;
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category;
|
import eu.kanade.tachiyomi.data.database.models.Category;
|
||||||
@ -21,25 +24,27 @@ import eu.kanade.tachiyomi.event.LibraryMangasEvent;
|
|||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
|
import rx.subjects.BehaviorSubject;
|
||||||
|
|
||||||
public class LibraryPresenter extends BasePresenter<LibraryFragment> {
|
public class LibraryPresenter extends BasePresenter<LibraryFragment> {
|
||||||
|
|
||||||
|
private static final int GET_LIBRARY = 1;
|
||||||
|
protected List<Category> categories;
|
||||||
|
protected List<Manga> selectedMangas;
|
||||||
|
protected BehaviorSubject<String> searchSubject;
|
||||||
@Inject DatabaseHelper db;
|
@Inject DatabaseHelper db;
|
||||||
@Inject PreferencesHelper preferences;
|
@Inject PreferencesHelper preferences;
|
||||||
@Inject CoverCache coverCache;
|
@Inject CoverCache coverCache;
|
||||||
@Inject SourceManager sourceManager;
|
@Inject SourceManager sourceManager;
|
||||||
|
|
||||||
protected List<Category> categories;
|
|
||||||
protected List<Manga> selectedMangas;
|
|
||||||
|
|
||||||
private static final int GET_LIBRARY = 1;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedState) {
|
protected void onCreate(Bundle savedState) {
|
||||||
super.onCreate(savedState);
|
super.onCreate(savedState);
|
||||||
|
|
||||||
selectedMangas = new ArrayList<>();
|
selectedMangas = new ArrayList<>();
|
||||||
|
|
||||||
|
searchSubject = BehaviorSubject.create();
|
||||||
|
|
||||||
restartableLatestCache(GET_LIBRARY,
|
restartableLatestCache(GET_LIBRARY,
|
||||||
this::getLibraryObservable,
|
this::getLibraryObservable,
|
||||||
(view, pair) -> view.onNextLibraryUpdate(pair.first, pair.second));
|
(view, pair) -> view.onNextLibraryUpdate(pair.first, pair.second));
|
||||||
@ -50,9 +55,9 @@ public class LibraryPresenter extends BasePresenter<LibraryFragment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDropView() {
|
||||||
EventBus.getDefault().removeStickyEvent(LibraryMangasEvent.class);
|
EventBus.getDefault().removeStickyEvent(LibraryMangasEvent.class);
|
||||||
super.onDestroy();
|
super.onDropView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -135,4 +140,18 @@ public class LibraryPresenter extends BasePresenter<LibraryFragment> {
|
|||||||
|
|
||||||
db.setMangaCategories(mc, mangas);
|
db.setMangaCategories(mc, mangas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update cover with local file
|
||||||
|
*/
|
||||||
|
public boolean editCoverWithLocalFile(File file, Manga manga) throws IOException {
|
||||||
|
if (!manga.initialized)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (manga.favorite) {
|
||||||
|
coverCache.copyToLocalCache(manga.thumbnail_url, file);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,12 @@ import android.support.v4.content.ContextCompat;
|
|||||||
import android.support.v4.view.ViewPager;
|
import android.support.v4.view.ViewPager;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import de.greenrobot.event.EventBus;
|
|
||||||
import eu.kanade.tachiyomi.App;
|
import eu.kanade.tachiyomi.App;
|
||||||
import eu.kanade.tachiyomi.R;
|
import eu.kanade.tachiyomi.R;
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
import eu.kanade.tachiyomi.data.database.models.Manga;
|
||||||
|
@ -2,14 +2,16 @@ package eu.kanade.tachiyomi.ui.manga;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import de.greenrobot.event.EventBus;
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
import eu.kanade.tachiyomi.data.database.models.Manga;
|
||||||
import eu.kanade.tachiyomi.event.MangaEvent;
|
import eu.kanade.tachiyomi.event.MangaEvent;
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
||||||
import eu.kanade.tachiyomi.util.EventBusHook;
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
|
|
||||||
@ -28,7 +30,7 @@ public class MangaPresenter extends BasePresenter<MangaActivity> {
|
|||||||
restartableLatestCache(GET_MANGA, this::getMangaObservable, MangaActivity::setManga);
|
restartableLatestCache(GET_MANGA, this::getMangaObservable, MangaActivity::setManga);
|
||||||
|
|
||||||
if (savedState == null)
|
if (savedState == null)
|
||||||
registerForStickyEvents();
|
registerForEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -43,8 +45,8 @@ public class MangaPresenter extends BasePresenter<MangaActivity> {
|
|||||||
.doOnNext(manga -> EventBus.getDefault().postSticky(new MangaEvent(manga)));
|
.doOnNext(manga -> EventBus.getDefault().postSticky(new MangaEvent(manga)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventBusHook
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
public void onEventMainThread(Manga manga) {
|
public void onEvent(Manga manga) {
|
||||||
EventBus.getDefault().removeStickyEvent(manga);
|
EventBus.getDefault().removeStickyEvent(manga);
|
||||||
unregisterForEvents();
|
unregisterForEvents();
|
||||||
this.manga = manga;
|
this.manga = manga;
|
||||||
|
@ -19,6 +19,7 @@ import android.widget.ImageView;
|
|||||||
|
|
||||||
import com.afollestad.materialdialogs.MaterialDialog;
|
import com.afollestad.materialdialogs.MaterialDialog;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
@ -99,6 +100,15 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
// Stop recycler's scrolling when onPause is called. If the activity is finishing
|
||||||
|
// the presenter will be destroyed, and it could cause NPE
|
||||||
|
// https://github.com/inorichi/tachiyomi/issues/159
|
||||||
|
recyclerView.stopScroll();
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.chapters, menu);
|
inflater.inflate(R.menu.chapters, menu);
|
||||||
@ -110,6 +120,9 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
|
|||||||
case R.id.action_display_mode:
|
case R.id.action_display_mode:
|
||||||
showDisplayModeDialog();
|
showDisplayModeDialog();
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.manga_download:
|
||||||
|
showDownloadDialog();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -164,9 +177,9 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
|
|||||||
swipeRefresh.setRefreshing(false);
|
swipeRefresh.setRefreshing(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onFetchChaptersError() {
|
public void onFetchChaptersError(Throwable error) {
|
||||||
swipeRefresh.setRefreshing(false);
|
swipeRefresh.setRefreshing(false);
|
||||||
ToastUtil.showShort(getContext(), R.string.fetch_chapters_error);
|
ToastUtil.showShort(getContext(), error.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCatalogueManga() {
|
public boolean isCatalogueManga() {
|
||||||
@ -190,6 +203,7 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
|
|||||||
int selectedIndex = manga.getDisplayMode() == Manga.DISPLAY_NAME ? 0 : 1;
|
int selectedIndex = manga.getDisplayMode() == Manga.DISPLAY_NAME ? 0 : 1;
|
||||||
|
|
||||||
new MaterialDialog.Builder(getActivity())
|
new MaterialDialog.Builder(getActivity())
|
||||||
|
.title(R.string.action_display_mode)
|
||||||
.items(modes)
|
.items(modes)
|
||||||
.itemsIds(ids)
|
.itemsIds(ids)
|
||||||
.itemsCallbackSingleChoice(selectedIndex, (dialog, itemView, which, text) -> {
|
.itemsCallbackSingleChoice(selectedIndex, (dialog, itemView, which, text) -> {
|
||||||
@ -202,6 +216,32 @@ public class ChaptersFragment extends BaseRxFragment<ChaptersPresenter> implemen
|
|||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showDownloadDialog() {
|
||||||
|
|
||||||
|
// Get available modes
|
||||||
|
String[] modes = {getString(R.string.download_all), getString(R.string.download_unread)};
|
||||||
|
|
||||||
|
new MaterialDialog.Builder(getActivity())
|
||||||
|
.title(R.string.manga_download)
|
||||||
|
.items(modes)
|
||||||
|
.itemsCallback((dialog, view, i, charSequence) -> {
|
||||||
|
List<Chapter> chapters = new ArrayList<>();
|
||||||
|
|
||||||
|
for(Chapter chapter : getPresenter().getChapters()) {
|
||||||
|
if(!chapter.isDownloaded()) {
|
||||||
|
if(i == 0 || (i == 1 && !chapter.read)) {
|
||||||
|
chapters.add(chapter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(chapters.size() > 0) {
|
||||||
|
onDownload(Observable.from(chapters));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.negativeText(R.string.button_cancel)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
private void observeChapterDownloadProgress() {
|
private void observeChapterDownloadProgress() {
|
||||||
downloadProgressSubscription = getPresenter().getDownloadProgressObs()
|
downloadProgressSubscription = getPresenter().getDownloadProgressObs()
|
||||||
.subscribe(this::onDownloadProgressChange,
|
.subscribe(this::onDownloadProgressChange,
|
||||||
|
@ -2,14 +2,15 @@ package eu.kanade.tachiyomi.ui.manga.chapter;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.view.Menu;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.PopupMenu;
|
import android.widget.PopupMenu;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.text.DecimalFormatSymbols;
|
import java.text.DecimalFormatSymbols;
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
@ -23,23 +24,19 @@ import rx.Observable;
|
|||||||
|
|
||||||
public class ChaptersHolder extends FlexibleViewHolder {
|
public class ChaptersHolder extends FlexibleViewHolder {
|
||||||
|
|
||||||
|
private final ChaptersAdapter adapter;
|
||||||
|
private final int readColor;
|
||||||
|
private final int unreadColor;
|
||||||
|
private final DecimalFormat decimalFormat;
|
||||||
|
private final DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT);
|
||||||
@Bind(R.id.chapter_title) TextView title;
|
@Bind(R.id.chapter_title) TextView title;
|
||||||
@Bind(R.id.download_text) TextView downloadText;
|
@Bind(R.id.download_text) TextView downloadText;
|
||||||
@Bind(R.id.chapter_menu) RelativeLayout chapterMenu;
|
@Bind(R.id.chapter_menu) RelativeLayout chapterMenu;
|
||||||
@Bind(R.id.chapter_pages) TextView pages;
|
@Bind(R.id.chapter_pages) TextView pages;
|
||||||
@Bind(R.id.chapter_date) TextView date;
|
@Bind(R.id.chapter_date) TextView date;
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
private final ChaptersAdapter adapter;
|
|
||||||
private Chapter item;
|
private Chapter item;
|
||||||
|
|
||||||
private final int readColor;
|
|
||||||
private final int unreadColor;
|
|
||||||
|
|
||||||
private final DecimalFormat decimalFormat;
|
|
||||||
private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
|
|
||||||
|
|
||||||
public ChaptersHolder(View view, ChaptersAdapter adapter, OnListItemClickListener listener) {
|
public ChaptersHolder(View view, ChaptersAdapter adapter, OnListItemClickListener listener) {
|
||||||
super(view, adapter, listener);
|
super(view, adapter, listener);
|
||||||
this.adapter = adapter;
|
this.adapter = adapter;
|
||||||
@ -71,6 +68,7 @@ public class ChaptersHolder extends FlexibleViewHolder {
|
|||||||
}
|
}
|
||||||
title.setText(name);
|
title.setText(name);
|
||||||
title.setTextColor(chapter.read ? readColor : unreadColor);
|
title.setTextColor(chapter.read ? readColor : unreadColor);
|
||||||
|
date.setTextColor(chapter.read ? readColor : unreadColor);
|
||||||
|
|
||||||
if (!chapter.read && chapter.last_page_read > 0) {
|
if (!chapter.read && chapter.last_page_read > 0) {
|
||||||
pages.setText(context.getString(R.string.chapter_progress, chapter.last_page_read + 1));
|
pages.setText(context.getString(R.string.chapter_progress, chapter.last_page_read + 1));
|
||||||
@ -79,7 +77,7 @@ public class ChaptersHolder extends FlexibleViewHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onStatusChange(chapter.status);
|
onStatusChange(chapter.status);
|
||||||
date.setText(sdf.format(new Date(chapter.date_upload)));
|
date.setText(df.format(new Date(chapter.date_upload)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onStatusChange(int status) {
|
public void onStatusChange(int status) {
|
||||||
@ -109,19 +107,36 @@ public class ChaptersHolder extends FlexibleViewHolder {
|
|||||||
// Inflate our menu resource into the PopupMenu's Menu
|
// Inflate our menu resource into the PopupMenu's Menu
|
||||||
popup.getMenuInflater().inflate(R.menu.chapter_single, popup.getMenu());
|
popup.getMenuInflater().inflate(R.menu.chapter_single, popup.getMenu());
|
||||||
|
|
||||||
|
// Hide download and show delete if the chapter is downloaded and
|
||||||
|
if(item.isDownloaded()) {
|
||||||
|
Menu menu = popup.getMenu();
|
||||||
|
menu.findItem(R.id.action_download).setVisible(false);
|
||||||
|
menu.findItem(R.id.action_delete).setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide mark as unread when the chapter is unread
|
||||||
|
if(!item.read && item.last_page_read == 0) {
|
||||||
|
popup.getMenu().findItem(R.id.action_mark_as_unread).setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide mark as read when the chapter is read
|
||||||
|
if(item.read) {
|
||||||
|
popup.getMenu().findItem(R.id.action_mark_as_read).setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
// Set a listener so we are notified if a menu item is clicked
|
// Set a listener so we are notified if a menu item is clicked
|
||||||
popup.setOnMenuItemClickListener(menuItem -> {
|
popup.setOnMenuItemClickListener(menuItem -> {
|
||||||
Observable<Chapter> chapter = Observable.just(item);
|
Observable<Chapter> chapter = Observable.just(item);
|
||||||
|
|
||||||
switch (menuItem.getItemId()) {
|
switch (menuItem.getItemId()) {
|
||||||
case R.id.action_mark_as_read:
|
|
||||||
return adapter.getFragment().onMarkAsRead(chapter);
|
|
||||||
case R.id.action_mark_as_unread:
|
|
||||||
return adapter.getFragment().onMarkAsUnread(chapter);
|
|
||||||
case R.id.action_download:
|
case R.id.action_download:
|
||||||
return adapter.getFragment().onDownload(chapter);
|
return adapter.getFragment().onDownload(chapter);
|
||||||
case R.id.action_delete:
|
case R.id.action_delete:
|
||||||
return adapter.getFragment().onDelete(chapter);
|
return adapter.getFragment().onDelete(chapter);
|
||||||
|
case R.id.action_mark_as_read:
|
||||||
|
return adapter.getFragment().onMarkAsRead(chapter);
|
||||||
|
case R.id.action_mark_as_unread:
|
||||||
|
return adapter.getFragment().onMarkAsUnread(chapter);
|
||||||
case R.id.action_mark_previous_as_read:
|
case R.id.action_mark_previous_as_read:
|
||||||
return adapter.getFragment().onMarkPreviousAsRead(item);
|
return adapter.getFragment().onMarkPreviousAsRead(item);
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,14 @@ package eu.kanade.tachiyomi.ui.manga.chapter;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import de.greenrobot.event.EventBus;
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
import eu.kanade.tachiyomi.data.database.models.Manga;
|
||||||
@ -21,7 +24,6 @@ import eu.kanade.tachiyomi.event.DownloadChaptersEvent;
|
|||||||
import eu.kanade.tachiyomi.event.MangaEvent;
|
import eu.kanade.tachiyomi.event.MangaEvent;
|
||||||
import eu.kanade.tachiyomi.event.ReaderEvent;
|
import eu.kanade.tachiyomi.event.ReaderEvent;
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
||||||
import eu.kanade.tachiyomi.util.EventBusHook;
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
@ -52,38 +54,27 @@ public class ChaptersPresenter extends BasePresenter<ChaptersFragment> {
|
|||||||
protected void onCreate(Bundle savedState) {
|
protected void onCreate(Bundle savedState) {
|
||||||
super.onCreate(savedState);
|
super.onCreate(savedState);
|
||||||
|
|
||||||
if (savedState != null) {
|
|
||||||
onProcessRestart();
|
|
||||||
}
|
|
||||||
|
|
||||||
chaptersSubject = PublishSubject.create();
|
chaptersSubject = PublishSubject.create();
|
||||||
|
|
||||||
restartableLatestCache(GET_MANGA,
|
startableLatestCache(GET_MANGA,
|
||||||
() -> Observable.just(manga),
|
() -> Observable.just(manga),
|
||||||
ChaptersFragment::onNextManga);
|
ChaptersFragment::onNextManga);
|
||||||
|
|
||||||
restartableLatestCache(DB_CHAPTERS,
|
startableLatestCache(DB_CHAPTERS,
|
||||||
this::getDbChaptersObs,
|
this::getDbChaptersObs,
|
||||||
ChaptersFragment::onNextChapters);
|
ChaptersFragment::onNextChapters);
|
||||||
|
|
||||||
restartableFirst(FETCH_CHAPTERS,
|
startableFirst(FETCH_CHAPTERS,
|
||||||
this::getOnlineChaptersObs,
|
this::getOnlineChaptersObs,
|
||||||
(view, result) -> view.onFetchChaptersDone(),
|
(view, result) -> view.onFetchChaptersDone(),
|
||||||
(view, error) -> view.onFetchChaptersError());
|
(view, error) -> view.onFetchChaptersError(error));
|
||||||
|
|
||||||
restartableLatestCache(CHAPTER_STATUS_CHANGES,
|
startableLatestCache(CHAPTER_STATUS_CHANGES,
|
||||||
this::getChapterStatusObs,
|
this::getChapterStatusObs,
|
||||||
(view, download) -> view.onChapterStatusChange(download),
|
(view, download) -> view.onChapterStatusChange(download),
|
||||||
(view, error) -> Timber.e(error.getCause(), error.getMessage()));
|
(view, error) -> Timber.e(error.getCause(), error.getMessage()));
|
||||||
|
|
||||||
registerForStickyEvents();
|
registerForEvents();
|
||||||
}
|
|
||||||
|
|
||||||
private void onProcessRestart() {
|
|
||||||
stop(GET_MANGA);
|
|
||||||
stop(DB_CHAPTERS);
|
|
||||||
stop(FETCH_CHAPTERS);
|
|
||||||
stop(CHAPTER_STATUS_CHANGES);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -93,8 +84,8 @@ public class ChaptersPresenter extends BasePresenter<ChaptersFragment> {
|
|||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventBusHook
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
public void onEventMainThread(MangaEvent event) {
|
public void onEvent(MangaEvent event) {
|
||||||
this.manga = event.manga;
|
this.manga = event.manga;
|
||||||
start(GET_MANGA);
|
start(GET_MANGA);
|
||||||
|
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.ui.manga.info;
|
package eu.kanade.tachiyomi.ui.manga.info;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.design.widget.FloatingActionButton;
|
import android.support.design.widget.FloatingActionButton;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
@ -10,178 +7,237 @@ import android.support.v4.widget.SwipeRefreshLayout;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.bumptech.glide.load.model.LazyHeaders;
|
import com.bumptech.glide.load.model.LazyHeaders;
|
||||||
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
|
|
||||||
import com.mikepenz.iconics.IconicsDrawable;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import eu.kanade.tachiyomi.R;
|
import eu.kanade.tachiyomi.R;
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache;
|
import eu.kanade.tachiyomi.data.cache.CoverCache;
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
import eu.kanade.tachiyomi.data.database.models.Manga;
|
||||||
import eu.kanade.tachiyomi.data.io.IOHandler;
|
|
||||||
import eu.kanade.tachiyomi.data.source.base.Source;
|
import eu.kanade.tachiyomi.data.source.base.Source;
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
|
||||||
import eu.kanade.tachiyomi.util.ToastUtil;
|
|
||||||
import nucleus.factory.RequiresPresenter;
|
import nucleus.factory.RequiresPresenter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment that shows manga information.
|
||||||
|
* Uses R.layout.fragment_manga_info.
|
||||||
|
* UI related actions should be called from here.
|
||||||
|
*/
|
||||||
@RequiresPresenter(MangaInfoPresenter.class)
|
@RequiresPresenter(MangaInfoPresenter.class)
|
||||||
public class MangaInfoFragment extends BaseRxFragment<MangaInfoPresenter> {
|
public class MangaInfoFragment extends BaseRxFragment<MangaInfoPresenter> {
|
||||||
|
/**
|
||||||
private static final int REQUEST_IMAGE_OPEN = 101;
|
* SwipeRefreshLayout showing refresh status
|
||||||
|
*/
|
||||||
@Bind(R.id.swipe_refresh) SwipeRefreshLayout swipeRefresh;
|
@Bind(R.id.swipe_refresh) SwipeRefreshLayout swipeRefresh;
|
||||||
@Bind(R.id.manga_artist) TextView artist;
|
|
||||||
@Bind(R.id.manga_author) TextView author;
|
|
||||||
@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;
|
|
||||||
@Bind(R.id.action_favorite) Button favoriteBtn;
|
|
||||||
@Bind(R.id.fab_edit) FloatingActionButton fabEdit;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextView containing artist information.
|
||||||
|
*/
|
||||||
|
@Bind(R.id.manga_artist) TextView artist;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextView containing author information.
|
||||||
|
*/
|
||||||
|
@Bind(R.id.manga_author) TextView author;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextView containing chapter count.
|
||||||
|
*/
|
||||||
|
@Bind(R.id.manga_chapters) TextView chapterCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextView containing genres.
|
||||||
|
*/
|
||||||
|
@Bind(R.id.manga_genres) TextView genres;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextView containing status (ongoing, finished).
|
||||||
|
*/
|
||||||
|
@Bind(R.id.manga_status) TextView status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextView containing source.
|
||||||
|
*/
|
||||||
|
@Bind(R.id.manga_source) TextView source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextView containing manga summary.
|
||||||
|
*/
|
||||||
|
@Bind(R.id.manga_summary) TextView description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImageView of cover.
|
||||||
|
*/
|
||||||
|
@Bind(R.id.manga_cover) ImageView cover;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImageView containing manga cover shown as blurred backdrop.
|
||||||
|
*/
|
||||||
|
@Bind(R.id.backdrop) ImageView backdrop;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FAB anchored to bottom of top view used to (add / remove) manga (to / from) library.
|
||||||
|
*/
|
||||||
|
@Bind(R.id.fab_favorite) FloatingActionButton fabFavorite;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new instance of MangaInfoFragment.
|
||||||
|
*
|
||||||
|
* @return MangaInfoFragment.
|
||||||
|
*/
|
||||||
public static MangaInfoFragment newInstance() {
|
public static MangaInfoFragment newInstance() {
|
||||||
return new MangaInfoFragment();
|
return new MangaInfoFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedState) {
|
|
||||||
super.onCreate(savedState);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
// Inflate the layout for this fragment
|
// Inflate the layout for this fragment.
|
||||||
View view = inflater.inflate(R.layout.fragment_manga_info, container, false);
|
View view = inflater.inflate(R.layout.fragment_manga_info, container, false);
|
||||||
|
|
||||||
|
// Bind layout objects.
|
||||||
ButterKnife.bind(this, view);
|
ButterKnife.bind(this, view);
|
||||||
|
|
||||||
//Create edit drawable with size 24dp (google guidelines)
|
// Set onclickListener to toggle favorite when FAB clicked.
|
||||||
IconicsDrawable edit = new IconicsDrawable(this.getContext())
|
fabFavorite.setOnClickListener(v -> getPresenter().toggleFavorite());
|
||||||
.icon(GoogleMaterial.Icon.gmd_edit)
|
|
||||||
.color(ContextCompat.getColor(this.getContext(), R.color.white))
|
|
||||||
.sizeDp(24);
|
|
||||||
|
|
||||||
// Update image of fab button
|
|
||||||
fabEdit.setImageDrawable(edit);
|
|
||||||
|
|
||||||
// Set listener.
|
|
||||||
fabEdit.setOnClickListener(v -> selectImage());
|
|
||||||
|
|
||||||
favoriteBtn.setOnClickListener(v -> getPresenter().toggleFavorite());
|
|
||||||
|
|
||||||
|
// Set SwipeRefresh to refresh manga data.
|
||||||
swipeRefresh.setOnRefreshListener(this::fetchMangaFromSource);
|
swipeRefresh.setOnRefreshListener(this::fetchMangaFromSource);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if manga is initialized.
|
||||||
|
* If true update view with manga information,
|
||||||
|
* if false fetch manga information
|
||||||
|
*
|
||||||
|
* @param manga manga object containing information about manga.
|
||||||
|
* @param source the source of the manga.
|
||||||
|
*/
|
||||||
public void onNextManga(Manga manga, Source source) {
|
public void onNextManga(Manga manga, Source source) {
|
||||||
if (manga.initialized) {
|
if (manga.initialized) {
|
||||||
|
// Update view.
|
||||||
setMangaInfo(manga, source);
|
setMangaInfo(manga, source);
|
||||||
} else {
|
} else {
|
||||||
// Initialize manga
|
// Initialize manga.
|
||||||
fetchMangaFromSource();
|
fetchMangaFromSource();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the info of the manga
|
* Update the view with manga information.
|
||||||
*
|
*
|
||||||
* @param manga manga object containing information about manga
|
* @param manga manga object containing information about manga.
|
||||||
* @param mangaSource the source of the manga
|
* @param mangaSource the source of the manga.
|
||||||
*/
|
*/
|
||||||
private void setMangaInfo(Manga manga, Source mangaSource) {
|
private void setMangaInfo(Manga manga, Source mangaSource) {
|
||||||
|
// Update artist TextView.
|
||||||
artist.setText(manga.artist);
|
artist.setText(manga.artist);
|
||||||
|
|
||||||
|
// Update author TextView.
|
||||||
author.setText(manga.author);
|
author.setText(manga.author);
|
||||||
|
|
||||||
|
// If manga source is known update source TextView.
|
||||||
if (mangaSource != null) {
|
if (mangaSource != null) {
|
||||||
source.setText(mangaSource.getName());
|
source.setText(mangaSource.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update genres TextView.
|
||||||
genres.setText(manga.genre);
|
genres.setText(manga.genre);
|
||||||
|
|
||||||
|
// Update status TextView.
|
||||||
status.setText(manga.getStatus(getActivity()));
|
status.setText(manga.getStatus(getActivity()));
|
||||||
|
|
||||||
|
// Update description TextView.
|
||||||
description.setText(manga.description);
|
description.setText(manga.description);
|
||||||
|
|
||||||
setFavoriteText(manga.favorite);
|
// Set the favorite drawable to the correct one.
|
||||||
|
setFavoriteDrawable(manga.favorite);
|
||||||
|
|
||||||
|
// Initialize CoverCache and Glide headers to retrieve cover information.
|
||||||
CoverCache coverCache = getPresenter().coverCache;
|
CoverCache coverCache = getPresenter().coverCache;
|
||||||
LazyHeaders headers = getPresenter().source.getGlideHeaders();
|
LazyHeaders headers = getPresenter().source.getGlideHeaders();
|
||||||
if (manga.thumbnail_url != null && cover.getDrawable() == null) {
|
|
||||||
|
// Check if thumbnail_url is given.
|
||||||
|
if (manga.thumbnail_url != null) {
|
||||||
|
// Check if cover is already drawn.
|
||||||
|
if (cover.getDrawable() == null) {
|
||||||
|
// If manga is in library then (download / save) (from / to) local cache if available,
|
||||||
|
// else download from network.
|
||||||
if (manga.favorite) {
|
if (manga.favorite) {
|
||||||
coverCache.saveOrLoadFromCache(cover, manga.thumbnail_url, headers);
|
coverCache.saveOrLoadFromCache(cover, manga.thumbnail_url, headers);
|
||||||
} else {
|
} else {
|
||||||
coverCache.loadFromNetwork(cover, manga.thumbnail_url, headers);
|
coverCache.loadFromNetwork(cover, manga.thumbnail_url, headers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Check if backdrop is already drawn.
|
||||||
|
if (backdrop.getDrawable() == null) {
|
||||||
|
// If manga is in library then (download / save) (from / to) local cache if available,
|
||||||
|
// else download from network.
|
||||||
|
if (manga.favorite) {
|
||||||
|
coverCache.saveOrLoadFromCache(backdrop, manga.thumbnail_url, headers);
|
||||||
|
} else {
|
||||||
|
coverCache.loadFromNetwork(backdrop, manga.thumbnail_url, headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update chapter count TextView.
|
||||||
|
*
|
||||||
|
* @param count number of chapters.
|
||||||
|
*/
|
||||||
public void setChapterCount(int count) {
|
public void setChapterCount(int count) {
|
||||||
chapterCount.setText(String.valueOf(count));
|
chapterCount.setText(String.valueOf(count));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setFavoriteText(boolean isFavorite) {
|
/**
|
||||||
favoriteBtn.setText(!isFavorite ? R.string.add_to_library : R.string.remove_from_library);
|
* Update FAB with correct drawable.
|
||||||
|
*
|
||||||
|
* @param isFavorite determines if manga is favorite or not.
|
||||||
|
*/
|
||||||
|
private void setFavoriteDrawable(boolean isFavorite) {
|
||||||
|
// Set the Favorite drawable to the correct one.
|
||||||
|
// Border drawable if false, filled drawable if true.
|
||||||
|
fabFavorite.setImageDrawable(ContextCompat.getDrawable(getContext(), isFavorite ?
|
||||||
|
R.drawable.ic_bookmark_white_24dp :
|
||||||
|
R.drawable.ic_bookmark_border_white_24dp));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start fetching manga information from source.
|
||||||
|
*/
|
||||||
private void fetchMangaFromSource() {
|
private void fetchMangaFromSource() {
|
||||||
setRefreshing(true);
|
setRefreshing(true);
|
||||||
|
// Call presenter and start fetching manga information
|
||||||
getPresenter().fetchMangaFromSource();
|
getPresenter().fetchMangaFromSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectImage() {
|
|
||||||
if (getPresenter().getManga().favorite) {
|
|
||||||
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setType("image/*");
|
|
||||||
intent.setAction(Intent.ACTION_GET_CONTENT);
|
|
||||||
startActivityForResult(Intent.createChooser(intent,
|
|
||||||
getString(R.string.file_select_cover)), REQUEST_IMAGE_OPEN);
|
|
||||||
} else {
|
|
||||||
ToastUtil.showShort(getContext(), R.string.notification_first_add_to_library);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_IMAGE_OPEN) {
|
|
||||||
// Get the file's content URI from the incoming Intent
|
|
||||||
Uri selectedImageUri = data.getData();
|
|
||||||
|
|
||||||
// Convert to absolute path to prevent FileNotFoundException
|
|
||||||
String result = IOHandler.getFilePath(selectedImageUri,
|
|
||||||
getContext().getContentResolver(), getContext());
|
|
||||||
|
|
||||||
// Get file from filepath
|
|
||||||
File picture = new File(result != null ? result : "");
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Update cover to selected file, show error if something went wrong
|
|
||||||
if (!getPresenter().editCoverWithLocalFile(picture, cover))
|
|
||||||
ToastUtil.showShort(getContext(), R.string.notification_manga_update_failed);
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update swipeRefresh to stop showing refresh in progress spinner.
|
||||||
|
*/
|
||||||
public void onFetchMangaDone() {
|
public void onFetchMangaDone() {
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update swipeRefresh to start showing refresh in progress spinner.
|
||||||
|
*/
|
||||||
public void onFetchMangaError() {
|
public void onFetchMangaError() {
|
||||||
setRefreshing(false);
|
setRefreshing(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set swipeRefresh status.
|
||||||
|
*
|
||||||
|
* @param value status of manga fetch.
|
||||||
|
*/
|
||||||
private void setRefreshing(boolean value) {
|
private void setRefreshing(boolean value) {
|
||||||
swipeRefresh.setRefreshing(value);
|
swipeRefresh.setRefreshing(value);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
package eu.kanade.tachiyomi.ui.manga.info;
|
package eu.kanade.tachiyomi.ui.manga.info;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import java.io.File;
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
import java.io.IOException;
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@ -16,47 +15,59 @@ import eu.kanade.tachiyomi.data.source.base.Source;
|
|||||||
import eu.kanade.tachiyomi.event.ChapterCountEvent;
|
import eu.kanade.tachiyomi.event.ChapterCountEvent;
|
||||||
import eu.kanade.tachiyomi.event.MangaEvent;
|
import eu.kanade.tachiyomi.event.MangaEvent;
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
||||||
import eu.kanade.tachiyomi.util.EventBusHook;
|
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
import rx.schedulers.Schedulers;
|
import rx.schedulers.Schedulers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter of MangaInfoFragment.
|
||||||
|
* Contains information and data for fragment.
|
||||||
|
* Observable updates should be called from here.
|
||||||
|
*/
|
||||||
public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
|
public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id of the restartable.
|
* The id of the restartable.
|
||||||
*/
|
*/
|
||||||
private static final int GET_MANGA = 1;
|
private static final int GET_MANGA = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id of the restartable.
|
* The id of the restartable.
|
||||||
*/
|
*/
|
||||||
private static final int GET_CHAPTER_COUNT = 2;
|
private static final int GET_CHAPTER_COUNT = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The id of the restartable.
|
* The id of the restartable.
|
||||||
*/
|
*/
|
||||||
private static final int FETCH_MANGA_INFO = 3;
|
private static final int FETCH_MANGA_INFO = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source information
|
* Source information.
|
||||||
*/
|
*/
|
||||||
protected Source source;
|
protected Source source;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to connect to database
|
* Used to connect to database.
|
||||||
*/
|
*/
|
||||||
@Inject DatabaseHelper db;
|
@Inject DatabaseHelper db;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to connect to different manga sources
|
* Used to connect to different manga sources.
|
||||||
*/
|
*/
|
||||||
@Inject SourceManager sourceManager;
|
@Inject SourceManager sourceManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to connect to cache
|
* Used to connect to cache.
|
||||||
*/
|
*/
|
||||||
@Inject CoverCache coverCache;
|
@Inject CoverCache coverCache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selected manga information
|
* Selected manga information.
|
||||||
*/
|
*/
|
||||||
private Manga manga;
|
private Manga manga;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count of chapters
|
* Count of chapters.
|
||||||
*/
|
*/
|
||||||
private int count = -1;
|
private int count = -1;
|
||||||
|
|
||||||
@ -64,37 +75,24 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
|
|||||||
protected void onCreate(Bundle savedState) {
|
protected void onCreate(Bundle savedState) {
|
||||||
super.onCreate(savedState);
|
super.onCreate(savedState);
|
||||||
|
|
||||||
if (savedState != null) {
|
// Notify the view a manga is available or has changed.
|
||||||
onProcessRestart();
|
startableLatestCache(GET_MANGA,
|
||||||
}
|
|
||||||
|
|
||||||
// Update manga cache
|
|
||||||
restartableLatestCache(GET_MANGA,
|
|
||||||
() -> Observable.just(manga),
|
() -> Observable.just(manga),
|
||||||
(view, manga) -> view.onNextManga(manga, source));
|
(view, manga) -> view.onNextManga(manga, source));
|
||||||
|
|
||||||
// Update chapter count
|
// Update chapter count.
|
||||||
restartableLatestCache(GET_CHAPTER_COUNT,
|
startableLatestCache(GET_CHAPTER_COUNT,
|
||||||
() -> Observable.just(count),
|
() -> Observable.just(count),
|
||||||
MangaInfoFragment::setChapterCount);
|
MangaInfoFragment::setChapterCount);
|
||||||
|
|
||||||
// Fetch manga info from source
|
// Fetch manga info from source.
|
||||||
restartableFirst(FETCH_MANGA_INFO,
|
startableFirst(FETCH_MANGA_INFO,
|
||||||
this::fetchMangaObs,
|
this::fetchMangaObs,
|
||||||
(view, manga) -> view.onFetchMangaDone(),
|
(view, manga) -> view.onFetchMangaDone(),
|
||||||
(view, error) -> view.onFetchMangaError());
|
(view, error) -> view.onFetchMangaError());
|
||||||
|
|
||||||
// onEventMainThread receives an event thanks to this line.
|
// Listen for events.
|
||||||
registerForStickyEvents();
|
registerForEvents();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when savedState not null
|
|
||||||
*/
|
|
||||||
private void onProcessRestart() {
|
|
||||||
stop(GET_MANGA);
|
|
||||||
stop(GET_CHAPTER_COUNT);
|
|
||||||
stop(FETCH_MANGA_INFO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -103,23 +101,24 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
|
|||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventBusHook
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
public void onEventMainThread(MangaEvent event) {
|
public void onEvent(MangaEvent event) {
|
||||||
this.manga = event.manga;
|
this.manga = event.manga;
|
||||||
source = sourceManager.get(manga.source);
|
source = sourceManager.get(manga.source);
|
||||||
refreshManga();
|
refreshManga();
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventBusHook
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
public void onEventMainThread(ChapterCountEvent event) {
|
public void onEvent(ChapterCountEvent event) {
|
||||||
if (count != event.getCount()) {
|
if (count != event.getCount()) {
|
||||||
count = event.getCount();
|
count = event.getCount();
|
||||||
|
// Update chapter count
|
||||||
start(GET_CHAPTER_COUNT);
|
start(GET_CHAPTER_COUNT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch manga info from source
|
* Fetch manga information from source.
|
||||||
*/
|
*/
|
||||||
public void fetchMangaFromSource() {
|
public void fetchMangaFromSource() {
|
||||||
if (isUnsubscribed(FETCH_MANGA_INFO)) {
|
if (isUnsubscribed(FETCH_MANGA_INFO)) {
|
||||||
@ -127,6 +126,11 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch manga information from source.
|
||||||
|
*
|
||||||
|
* @return manga information.
|
||||||
|
*/
|
||||||
private Observable<Manga> fetchMangaObs() {
|
private Observable<Manga> fetchMangaObs() {
|
||||||
return source.pullMangaFromNetwork(manga.url)
|
return source.pullMangaFromNetwork(manga.url)
|
||||||
.flatMap(networkManga -> {
|
.flatMap(networkManga -> {
|
||||||
@ -139,6 +143,9 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
|
|||||||
.doOnNext(manga -> refreshManga());
|
.doOnNext(manga -> refreshManga());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update favorite status of manga, (removes / adds) manga (to / from) library.
|
||||||
|
*/
|
||||||
public void toggleFavorite() {
|
public void toggleFavorite() {
|
||||||
manga.favorite = !manga.favorite;
|
manga.favorite = !manga.favorite;
|
||||||
onMangaFavoriteChange(manga.favorite);
|
onMangaFavoriteChange(manga.favorite);
|
||||||
@ -146,21 +153,12 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
|
|||||||
refreshManga();
|
refreshManga();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update cover with local file
|
* (Removes / Saves) cover depending on favorite status.
|
||||||
|
*
|
||||||
|
* @param isFavorite determines if manga is favorite or not.
|
||||||
*/
|
*/
|
||||||
public boolean editCoverWithLocalFile(File file, ImageView imageView) throws IOException {
|
|
||||||
if (!manga.initialized)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (manga.favorite) {
|
|
||||||
coverCache.copyToLocalCache(manga.thumbnail_url, file);
|
|
||||||
coverCache.saveOrLoadFromCache(imageView, manga.thumbnail_url, source.getGlideHeaders());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onMangaFavoriteChange(boolean isFavorite) {
|
private void onMangaFavoriteChange(boolean isFavorite) {
|
||||||
if (isFavorite) {
|
if (isFavorite) {
|
||||||
coverCache.save(manga.thumbnail_url, source.getGlideHeaders());
|
coverCache.save(manga.thumbnail_url, source.getGlideHeaders());
|
||||||
@ -169,12 +167,10 @@ public class MangaInfoPresenter extends BasePresenter<MangaInfoFragment> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Manga getManga() {
|
/**
|
||||||
return manga;
|
* Refresh MangaInfo view.
|
||||||
}
|
*/
|
||||||
|
private void refreshManga() {
|
||||||
// Used to refresh the view
|
|
||||||
protected void refreshManga() {
|
|
||||||
start(GET_MANGA);
|
start(GET_MANGA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ import android.view.View;
|
|||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.afollestad.materialdialogs.MaterialDialog;
|
import com.afollestad.materialdialogs.MaterialDialog;
|
||||||
|
|
||||||
@ -25,11 +24,6 @@ import eu.kanade.tachiyomi.data.database.models.MangaSync;
|
|||||||
import rx.Subscription;
|
import rx.Subscription;
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
import rx.subjects.PublishSubject;
|
import rx.subjects.PublishSubject;
|
||||||
import uk.co.ribot.easyadapter.EasyAdapter;
|
|
||||||
import uk.co.ribot.easyadapter.ItemViewHolder;
|
|
||||||
import uk.co.ribot.easyadapter.PositionInfo;
|
|
||||||
import uk.co.ribot.easyadapter.annotations.LayoutId;
|
|
||||||
import uk.co.ribot.easyadapter.annotations.ViewId;
|
|
||||||
|
|
||||||
public class MyAnimeListDialogFragment extends DialogFragment {
|
public class MyAnimeListDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
@ -37,7 +31,7 @@ public class MyAnimeListDialogFragment extends DialogFragment {
|
|||||||
@Bind(R.id.myanimelist_search_results) ListView searchResults;
|
@Bind(R.id.myanimelist_search_results) ListView searchResults;
|
||||||
@Bind(R.id.progress) ProgressBar progressBar;
|
@Bind(R.id.progress) ProgressBar progressBar;
|
||||||
|
|
||||||
private EasyAdapter<MangaSync> adapter;
|
private MyAnimeListSearchAdapter adapter;
|
||||||
private MangaSync selectedItem;
|
private MangaSync selectedItem;
|
||||||
|
|
||||||
private Subscription searchSubscription;
|
private Subscription searchSubscription;
|
||||||
@ -59,7 +53,7 @@ public class MyAnimeListDialogFragment extends DialogFragment {
|
|||||||
ButterKnife.bind(this, dialog.getView());
|
ButterKnife.bind(this, dialog.getView());
|
||||||
|
|
||||||
// Create adapter
|
// Create adapter
|
||||||
adapter = new EasyAdapter<>(getActivity(), ResultViewHolder.class);
|
adapter = new MyAnimeListSearchAdapter(getActivity());
|
||||||
searchResults.setAdapter(adapter);
|
searchResults.setAdapter(adapter);
|
||||||
|
|
||||||
// Set listeners
|
// Set listeners
|
||||||
@ -125,7 +119,7 @@ public class MyAnimeListDialogFragment extends DialogFragment {
|
|||||||
public void onSearchResultsError() {
|
public void onSearchResultsError() {
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
searchResults.setVisibility(View.VISIBLE);
|
searchResults.setVisibility(View.VISIBLE);
|
||||||
adapter.getItems().clear();
|
adapter.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MyAnimeListFragment getMALFragment() {
|
public MyAnimeListFragment getMALFragment() {
|
||||||
@ -136,21 +130,6 @@ public class MyAnimeListDialogFragment extends DialogFragment {
|
|||||||
return getMALFragment().getPresenter();
|
return getMALFragment().getPresenter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@LayoutId(R.layout.dialog_myanimelist_search_item)
|
|
||||||
public static class ResultViewHolder extends ItemViewHolder<MangaSync> {
|
|
||||||
|
|
||||||
@ViewId(R.id.myanimelist_result_title) TextView title;
|
|
||||||
|
|
||||||
public ResultViewHolder(View view) {
|
|
||||||
super(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSetValues(MangaSync chapter, PositionInfo positionInfo) {
|
|
||||||
title.setText(chapter.title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SimpleTextChangeListener implements TextWatcher {
|
private static class SimpleTextChangeListener implements TextWatcher {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -4,6 +4,9 @@ import android.content.Context;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@ -16,7 +19,6 @@ import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager;
|
|||||||
import eu.kanade.tachiyomi.data.mangasync.services.MyAnimeList;
|
import eu.kanade.tachiyomi.data.mangasync.services.MyAnimeList;
|
||||||
import eu.kanade.tachiyomi.event.MangaEvent;
|
import eu.kanade.tachiyomi.event.MangaEvent;
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
||||||
import eu.kanade.tachiyomi.util.EventBusHook;
|
|
||||||
import eu.kanade.tachiyomi.util.ToastUtil;
|
import eu.kanade.tachiyomi.util.ToastUtil;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
@ -44,20 +46,16 @@ public class MyAnimeListPresenter extends BasePresenter<MyAnimeListFragment> {
|
|||||||
protected void onCreate(Bundle savedState) {
|
protected void onCreate(Bundle savedState) {
|
||||||
super.onCreate(savedState);
|
super.onCreate(savedState);
|
||||||
|
|
||||||
if (savedState != null) {
|
|
||||||
onProcessRestart();
|
|
||||||
}
|
|
||||||
|
|
||||||
myAnimeList = syncManager.getMyAnimeList();
|
myAnimeList = syncManager.getMyAnimeList();
|
||||||
|
|
||||||
restartableLatestCache(GET_MANGA_SYNC,
|
startableLatestCache(GET_MANGA_SYNC,
|
||||||
() -> db.getMangaSync(manga, myAnimeList).asRxObservable()
|
() -> db.getMangaSync(manga, myAnimeList).asRxObservable()
|
||||||
.doOnNext(mangaSync -> this.mangaSync = mangaSync)
|
.doOnNext(mangaSync -> this.mangaSync = mangaSync)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread()),
|
.observeOn(AndroidSchedulers.mainThread()),
|
||||||
MyAnimeListFragment::setMangaSync);
|
MyAnimeListFragment::setMangaSync);
|
||||||
|
|
||||||
restartableLatestCache(GET_SEARCH_RESULTS,
|
startableLatestCache(GET_SEARCH_RESULTS,
|
||||||
this::getSearchResultsObservable,
|
this::getSearchResultsObservable,
|
||||||
(view, results) -> {
|
(view, results) -> {
|
||||||
view.setSearchResults(results);
|
view.setSearchResults(results);
|
||||||
@ -66,7 +64,7 @@ public class MyAnimeListPresenter extends BasePresenter<MyAnimeListFragment> {
|
|||||||
view.setSearchResultsError();
|
view.setSearchResultsError();
|
||||||
});
|
});
|
||||||
|
|
||||||
restartableFirst(REFRESH,
|
startableFirst(REFRESH,
|
||||||
() -> myAnimeList.getList()
|
() -> myAnimeList.getList()
|
||||||
.flatMap(myList -> {
|
.flatMap(myList -> {
|
||||||
for (MangaSync myManga : myList) {
|
for (MangaSync myManga : myList) {
|
||||||
@ -86,16 +84,10 @@ public class MyAnimeListPresenter extends BasePresenter<MyAnimeListFragment> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onProcessRestart() {
|
|
||||||
stop(GET_MANGA_SYNC);
|
|
||||||
stop(GET_SEARCH_RESULTS);
|
|
||||||
stop(REFRESH);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onTakeView(MyAnimeListFragment view) {
|
protected void onTakeView(MyAnimeListFragment view) {
|
||||||
super.onTakeView(view);
|
super.onTakeView(view);
|
||||||
registerForStickyEvents();
|
registerForEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -104,8 +96,8 @@ public class MyAnimeListPresenter extends BasePresenter<MyAnimeListFragment> {
|
|||||||
super.onDropView();
|
super.onDropView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventBusHook
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
public void onEventMainThread(MangaEvent event) {
|
public void onEvent(MangaEvent event) {
|
||||||
this.manga = event.manga;
|
this.manga = event.manga;
|
||||||
start(GET_MANGA_SYNC);
|
start(GET_MANGA_SYNC);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.manga.myanimelist;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import butterknife.Bind;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import eu.kanade.tachiyomi.R;
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.MangaSync;
|
||||||
|
|
||||||
|
public class MyAnimeListSearchAdapter extends ArrayAdapter<MangaSync> {
|
||||||
|
|
||||||
|
public MyAnimeListSearchAdapter(Context context) {
|
||||||
|
super(context, R.layout.dialog_myanimelist_search_item, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View view, ViewGroup parent) {
|
||||||
|
// Get the data item for this position
|
||||||
|
MangaSync sync = getItem(position);
|
||||||
|
// Check if an existing view is being reused, otherwise inflate the view
|
||||||
|
SearchViewHolder holder; // view lookup cache stored in tag
|
||||||
|
if (view == null) {
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||||
|
view = inflater.inflate(R.layout.dialog_myanimelist_search_item, parent, false);
|
||||||
|
holder = new SearchViewHolder(view);
|
||||||
|
view.setTag(holder);
|
||||||
|
} else {
|
||||||
|
holder = (SearchViewHolder) view.getTag();
|
||||||
|
}
|
||||||
|
holder.onSetValues(sync);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItems(List<MangaSync> syncs) {
|
||||||
|
setNotifyOnChange(false);
|
||||||
|
clear();
|
||||||
|
addAll(syncs);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SearchViewHolder {
|
||||||
|
|
||||||
|
@Bind(R.id.myanimelist_result_title) TextView title;
|
||||||
|
|
||||||
|
public SearchViewHolder(View view) {
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSetValues(MangaSync sync) {
|
||||||
|
title.setText(sync.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.reader;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.ActivityInfo;
|
import android.content.pm.ActivityInfo;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -10,9 +11,9 @@ import android.support.annotation.NonNull;
|
|||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.Surface;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
@ -153,21 +154,57 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||||
|
int action = event.getAction();
|
||||||
|
int keyCode = event.getKeyCode();
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
||||||
|
if (action == KeyEvent.ACTION_UP && viewer != null)
|
||||||
|
viewer.moveToNext();
|
||||||
|
return true;
|
||||||
|
case KeyEvent.KEYCODE_VOLUME_UP:
|
||||||
|
if (action == KeyEvent.ACTION_UP && viewer != null)
|
||||||
|
viewer.moveToPrevious();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.dispatchKeyEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void onChapterError() {
|
public void onChapterError() {
|
||||||
finish();
|
finish();
|
||||||
ToastUtil.showShort(this, R.string.page_list_error);
|
ToastUtil.showShort(this, R.string.page_list_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onChapterReady(List<Page> pages, Manga manga, Chapter chapter, int currentPage) {
|
public void onChapterAppendError() {
|
||||||
if (currentPage == -1) {
|
// Ignore
|
||||||
currentPage = pages.size() - 1;
|
}
|
||||||
|
|
||||||
|
public void onChapterReady(Manga manga, Chapter chapter, Page currentPage) {
|
||||||
|
List<Page> pages = chapter.getPages();
|
||||||
|
if (currentPage == null) {
|
||||||
|
currentPage = pages.get(pages.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewer == null) {
|
if (viewer == null) {
|
||||||
viewer = getOrCreateViewer(manga);
|
viewer = getOrCreateViewer(manga);
|
||||||
}
|
}
|
||||||
viewer.onPageListReady(pages, currentPage);
|
viewer.onPageListReady(chapter, currentPage);
|
||||||
readerMenu.onChapterReady(pages.size(), manga, chapter, currentPage);
|
readerMenu.setActiveManga(manga);
|
||||||
|
readerMenu.setActiveChapter(chapter, currentPage.getPageNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEnterChapter(Chapter chapter, int currentPage) {
|
||||||
|
if (currentPage == -1) {
|
||||||
|
currentPage = chapter.getPages().size() - 1;
|
||||||
|
}
|
||||||
|
getPresenter().setActiveChapter(chapter);
|
||||||
|
readerMenu.setActiveChapter(chapter, currentPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAppendChapter(Chapter chapter) {
|
||||||
|
viewer.onPageListAppendReady(chapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onAdjacentChapters(Chapter previous, Chapter next) {
|
public void onAdjacentChapters(Chapter previous, Chapter next) {
|
||||||
@ -209,8 +246,9 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
|
|||||||
readerMenu.onPageChanged(currentPageIndex);
|
readerMenu.onPageChanged(currentPageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSelectedPage(int pageIndex) {
|
public void gotoPageInCurrentChapter(int pageIndex) {
|
||||||
viewer.setSelectedPage(pageIndex);
|
Page requestedPage = viewer.getCurrentPage().getChapter().getPages().get(pageIndex);
|
||||||
|
viewer.setSelectedPage(requestedPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onCenterSingleTap() {
|
public void onCenterSingleTap() {
|
||||||
@ -218,7 +256,7 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void requestNextChapter() {
|
public void requestNextChapter() {
|
||||||
getPresenter().setCurrentPage(viewer != null ? viewer.getCurrentPage() : 0);
|
getPresenter().setCurrentPage(viewer.getCurrentPage());
|
||||||
if (!getPresenter().loadNextChapter()) {
|
if (!getPresenter().loadNextChapter()) {
|
||||||
ToastUtil.showShort(this, R.string.no_next_chapter);
|
ToastUtil.showShort(this, R.string.no_next_chapter);
|
||||||
}
|
}
|
||||||
@ -226,7 +264,7 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void requestPreviousChapter() {
|
public void requestPreviousChapter() {
|
||||||
getPresenter().setCurrentPage(viewer != null ? viewer.getCurrentPage() : 0);
|
getPresenter().setCurrentPage(viewer.getCurrentPage());
|
||||||
if (!getPresenter().loadPreviousChapter()) {
|
if (!getPresenter().loadPreviousChapter()) {
|
||||||
ToastUtil.showShort(this, R.string.no_previous_chapter);
|
ToastUtil.showShort(this, R.string.no_previous_chapter);
|
||||||
}
|
}
|
||||||
@ -239,9 +277,9 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
|
|||||||
.asObservable()
|
.asObservable()
|
||||||
.subscribe(this::setPageNumberVisibility));
|
.subscribe(this::setPageNumberVisibility));
|
||||||
|
|
||||||
subscriptions.add(preferences.lockOrientation()
|
subscriptions.add(preferences.rotation()
|
||||||
.asObservable()
|
.asObservable()
|
||||||
.subscribe(this::setOrientation));
|
.subscribe(this::setRotation));
|
||||||
|
|
||||||
subscriptions.add(preferences.hideStatusBar()
|
subscriptions.add(preferences.hideStatusBar()
|
||||||
.asObservable()
|
.asObservable()
|
||||||
@ -261,28 +299,25 @@ public class ReaderActivity extends BaseRxActivity<ReaderPresenter> {
|
|||||||
.subscribe(this::applyTheme));
|
.subscribe(this::applyTheme));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setOrientation(boolean locked) {
|
private void setRotation(int rotation) {
|
||||||
if (locked) {
|
|
||||||
int orientation;
|
|
||||||
int rotation = ((WindowManager) getSystemService(
|
|
||||||
Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
|
|
||||||
switch (rotation) {
|
switch (rotation) {
|
||||||
case Surface.ROTATION_0:
|
// Rotation free
|
||||||
orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
|
case 1:
|
||||||
break;
|
|
||||||
case Surface.ROTATION_90:
|
|
||||||
orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
|
|
||||||
break;
|
|
||||||
case Surface.ROTATION_180:
|
|
||||||
orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
setRequestedOrientation(orientation);
|
|
||||||
} else {
|
|
||||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
||||||
|
break;
|
||||||
|
// Lock in current rotation
|
||||||
|
case 2:
|
||||||
|
int currentOrientation = getResources().getConfiguration().orientation;
|
||||||
|
setRotation(currentOrientation == Configuration.ORIENTATION_PORTRAIT ? 3 : 4);
|
||||||
|
break;
|
||||||
|
// Lock in portrait
|
||||||
|
case 3:
|
||||||
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
|
||||||
|
break;
|
||||||
|
// Lock in landscape
|
||||||
|
case 4:
|
||||||
|
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.reader;
|
|||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@ -43,9 +44,10 @@ public class ReaderMenu {
|
|||||||
@Bind(R.id.page_seeker) SeekBar seekBar;
|
@Bind(R.id.page_seeker) SeekBar seekBar;
|
||||||
@Bind(R.id.total_pages) TextView totalPages;
|
@Bind(R.id.total_pages) TextView totalPages;
|
||||||
@Bind(R.id.lock_orientation) ImageButton lockOrientation;
|
@Bind(R.id.lock_orientation) ImageButton lockOrientation;
|
||||||
|
@Bind(R.id.reader_zoom_selector) ImageButton zoomSelector;
|
||||||
|
@Bind(R.id.reader_scale_type_selector) ImageButton scaleTypeSelector;
|
||||||
@Bind(R.id.reader_selector) ImageButton readerSelector;
|
@Bind(R.id.reader_selector) ImageButton readerSelector;
|
||||||
@Bind(R.id.reader_extra_settings) ImageButton extraSettings;
|
@Bind(R.id.reader_extra_settings) ImageButton extraSettings;
|
||||||
@Bind(R.id.reader_scale_type_selector) ImageButton scaleTypeSelector;
|
|
||||||
|
|
||||||
private MenuItem nextChapterBtn;
|
private MenuItem nextChapterBtn;
|
||||||
private MenuItem prevChapterBtn;
|
private MenuItem prevChapterBtn;
|
||||||
@ -133,7 +135,7 @@ public class ReaderMenu {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onChapterReady(int numPages, Manga manga, Chapter chapter, int currentPageIndex) {
|
public void setActiveManga(Manga manga) {
|
||||||
if (manga.viewer == ReaderActivity.RIGHT_TO_LEFT && !inverted) {
|
if (manga.viewer == ReaderActivity.RIGHT_TO_LEFT && !inverted) {
|
||||||
// Invert the seekbar and textview fields for the right to left reader
|
// Invert the seekbar and textview fields for the right to left reader
|
||||||
seekBar.setRotation(180);
|
seekBar.setRotation(180);
|
||||||
@ -143,14 +145,17 @@ public class ReaderMenu {
|
|||||||
// Don't invert again on chapter change
|
// Don't invert again on chapter change
|
||||||
inverted = true;
|
inverted = true;
|
||||||
}
|
}
|
||||||
|
activity.setToolbarTitle(manga.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActiveChapter(Chapter chapter, int currentPageIndex) {
|
||||||
// Set initial values
|
// Set initial values
|
||||||
|
int numPages = chapter.getPages().size();
|
||||||
totalPages.setText("" + numPages);
|
totalPages.setText("" + numPages);
|
||||||
currentPage.setText("" + (currentPageIndex + 1));
|
currentPage.setText("" + (currentPageIndex + 1));
|
||||||
seekBar.setMax(numPages - 1);
|
seekBar.setMax(numPages - 1);
|
||||||
seekBar.setProgress(currentPageIndex);
|
seekBar.setProgress(currentPageIndex);
|
||||||
|
|
||||||
activity.setToolbarTitle(manga.title);
|
|
||||||
activity.setToolbarSubtitle(chapter.chapter_number != -1 ?
|
activity.setToolbarSubtitle(chapter.chapter_number != -1 ?
|
||||||
activity.getString(R.string.chapter_subtitle,
|
activity.getString(R.string.chapter_subtitle,
|
||||||
decimalFormat.format(chapter.chapter_number)) :
|
decimalFormat.format(chapter.chapter_number)) :
|
||||||
@ -174,24 +179,49 @@ public class ReaderMenu {
|
|||||||
if (nextChapterBtn != null) nextChapterBtn.setVisible(nextChapter != null);
|
if (nextChapterBtn != null) nextChapterBtn.setVisible(nextChapter != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
private void initializeMenu() {
|
private void initializeMenu() {
|
||||||
// Orientation changes
|
// Orientation selector
|
||||||
add(preferences.lockOrientation().asObservable()
|
add(preferences.rotation().asObservable()
|
||||||
.subscribe(locked -> {
|
.subscribe(value -> {
|
||||||
int resourceId = !locked ? R.drawable.ic_screen_rotation :
|
boolean isPortrait = activity.getResources().getConfiguration()
|
||||||
activity.getResources().getConfiguration().orientation == 1 ?
|
.orientation == Configuration.ORIENTATION_PORTRAIT;
|
||||||
|
int resourceId = value == 1 ? R.drawable.ic_screen_rotation : isPortrait ?
|
||||||
R.drawable.ic_screen_lock_portrait :
|
R.drawable.ic_screen_lock_portrait :
|
||||||
R.drawable.ic_screen_lock_landscape;
|
R.drawable.ic_screen_lock_landscape;
|
||||||
|
|
||||||
lockOrientation.setImageResource(resourceId);
|
lockOrientation.setImageResource(resourceId);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
lockOrientation.setOnClickListener(v ->
|
lockOrientation.setOnClickListener(v -> {
|
||||||
preferences.lockOrientation().set(!preferences.lockOrientation().get()));
|
showImmersiveDialog(new MaterialDialog.Builder(activity)
|
||||||
|
.title(R.string.pref_rotation_type)
|
||||||
|
.items(R.array.rotation_type)
|
||||||
|
.itemsCallbackSingleChoice(preferences.rotation().get() - 1,
|
||||||
|
(d, itemView, which, text) -> {
|
||||||
|
preferences.rotation().set(which + 1);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.build());
|
||||||
|
});
|
||||||
|
|
||||||
|
// Zoom selector
|
||||||
|
zoomSelector.setOnClickListener(v -> {
|
||||||
|
showImmersiveDialog(new MaterialDialog.Builder(activity)
|
||||||
|
.title(R.string.pref_zoom_start)
|
||||||
|
.items(R.array.zoom_start)
|
||||||
|
.itemsCallbackSingleChoice(preferences.zoomStart().get() - 1,
|
||||||
|
(d, itemView, which, text) -> {
|
||||||
|
preferences.zoomStart().set(which + 1);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.build());
|
||||||
|
});
|
||||||
|
|
||||||
// Scale type selector
|
// Scale type selector
|
||||||
scaleTypeSelector.setOnClickListener(v -> {
|
scaleTypeSelector.setOnClickListener(v -> {
|
||||||
showImmersiveDialog(new MaterialDialog.Builder(activity)
|
showImmersiveDialog(new MaterialDialog.Builder(activity)
|
||||||
|
.title(R.string.pref_image_scale_type)
|
||||||
.items(R.array.image_scale_type)
|
.items(R.array.image_scale_type)
|
||||||
.itemsCallbackSingleChoice(preferences.imageScaleType().get() - 1,
|
.itemsCallbackSingleChoice(preferences.imageScaleType().get() - 1,
|
||||||
(d, itemView, which, text) -> {
|
(d, itemView, which, text) -> {
|
||||||
@ -205,6 +235,7 @@ public class ReaderMenu {
|
|||||||
readerSelector.setOnClickListener(v -> {
|
readerSelector.setOnClickListener(v -> {
|
||||||
final Manga manga = activity.getPresenter().getManga();
|
final Manga manga = activity.getPresenter().getManga();
|
||||||
showImmersiveDialog(new MaterialDialog.Builder(activity)
|
showImmersiveDialog(new MaterialDialog.Builder(activity)
|
||||||
|
.title(R.string.pref_viewer_type)
|
||||||
.items(R.array.viewers_selector)
|
.items(R.array.viewers_selector)
|
||||||
.itemsCallbackSingleChoice(manga.viewer,
|
.itemsCallbackSingleChoice(manga.viewer,
|
||||||
(d, itemView, which, text) -> {
|
(d, itemView, which, text) -> {
|
||||||
@ -260,6 +291,7 @@ public class ReaderMenu {
|
|||||||
initializePopupMenu();
|
initializePopupMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
private void initializePopupMenu() {
|
private void initializePopupMenu() {
|
||||||
// Load values from preferences
|
// Load values from preferences
|
||||||
enableTransitions.setChecked(preferences.enableTransitions().get());
|
enableTransitions.setChecked(preferences.enableTransitions().get());
|
||||||
@ -337,7 +369,7 @@ public class ReaderMenu {
|
|||||||
@Override
|
@Override
|
||||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||||
if (fromUser) {
|
if (fromUser) {
|
||||||
activity.setSelectedPage(progress);
|
activity.gotoPageInCurrentChapter(progress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,17 +4,21 @@ import android.os.Bundle;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
import org.greenrobot.eventbus.Subscribe;
|
||||||
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import de.greenrobot.event.EventBus;
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga;
|
import eu.kanade.tachiyomi.data.database.models.Manga;
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaSync;
|
import eu.kanade.tachiyomi.data.database.models.MangaSync;
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager;
|
import eu.kanade.tachiyomi.data.download.DownloadManager;
|
||||||
|
import eu.kanade.tachiyomi.data.download.model.Download;
|
||||||
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager;
|
import eu.kanade.tachiyomi.data.mangasync.MangaSyncManager;
|
||||||
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService;
|
import eu.kanade.tachiyomi.data.mangasync.base.MangaSyncService;
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
|
||||||
@ -24,9 +28,9 @@ import eu.kanade.tachiyomi.data.source.model.Page;
|
|||||||
import eu.kanade.tachiyomi.data.sync.UpdateMangaSyncService;
|
import eu.kanade.tachiyomi.data.sync.UpdateMangaSyncService;
|
||||||
import eu.kanade.tachiyomi.event.ReaderEvent;
|
import eu.kanade.tachiyomi.event.ReaderEvent;
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
||||||
import eu.kanade.tachiyomi.util.EventBusHook;
|
|
||||||
import icepick.State;
|
import icepick.State;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
|
import rx.Subscription;
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
import rx.schedulers.Schedulers;
|
import rx.schedulers.Schedulers;
|
||||||
import rx.subjects.PublishSubject;
|
import rx.subjects.PublishSubject;
|
||||||
@ -41,72 +45,55 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
@Inject SourceManager sourceManager;
|
@Inject SourceManager sourceManager;
|
||||||
|
|
||||||
@State Manga manga;
|
@State Manga manga;
|
||||||
@State Chapter chapter;
|
@State Chapter activeChapter;
|
||||||
@State int sourceId;
|
@State int sourceId;
|
||||||
@State boolean isDownloaded;
|
@State int requestedPage;
|
||||||
@State int currentPage;
|
private Page currentPage;
|
||||||
private Source source;
|
private Source source;
|
||||||
private Chapter nextChapter;
|
private Chapter nextChapter;
|
||||||
private Chapter previousChapter;
|
private Chapter previousChapter;
|
||||||
private List<Page> pageList;
|
|
||||||
private List<Page> nextChapterPageList;
|
|
||||||
private List<MangaSync> mangaSyncList;
|
private List<MangaSync> mangaSyncList;
|
||||||
|
|
||||||
private PublishSubject<Page> retryPageSubject;
|
private PublishSubject<Page> retryPageSubject;
|
||||||
|
private PublishSubject<Chapter> pageInitializerSubject;
|
||||||
|
|
||||||
|
private boolean seamlessMode;
|
||||||
|
private Subscription appenderSubscription;
|
||||||
|
|
||||||
private static final int GET_PAGE_LIST = 1;
|
private static final int GET_PAGE_LIST = 1;
|
||||||
private static final int GET_PAGE_IMAGES = 2;
|
private static final int GET_ADJACENT_CHAPTERS = 2;
|
||||||
private static final int GET_ADJACENT_CHAPTERS = 3;
|
private static final int GET_MANGA_SYNC = 3;
|
||||||
private static final int RETRY_IMAGES = 4;
|
private static final int PRELOAD_NEXT_CHAPTER = 4;
|
||||||
private static final int PRELOAD_NEXT_CHAPTER = 5;
|
|
||||||
private static final int GET_MANGA_SYNC = 6;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedState) {
|
protected void onCreate(Bundle savedState) {
|
||||||
super.onCreate(savedState);
|
super.onCreate(savedState);
|
||||||
|
|
||||||
if (savedState != null) {
|
if (savedState != null) {
|
||||||
onProcessRestart();
|
source = sourceManager.get(sourceId);
|
||||||
|
initializeSubjects();
|
||||||
}
|
}
|
||||||
|
|
||||||
retryPageSubject = PublishSubject.create();
|
seamlessMode = prefs.seamlessMode();
|
||||||
|
|
||||||
restartableLatestCache(PRELOAD_NEXT_CHAPTER,
|
startableLatestCache(GET_ADJACENT_CHAPTERS, this::getAdjacentChaptersObservable,
|
||||||
this::getPreloadNextChapterObservable,
|
(view, pair) -> view.onAdjacentChapters(pair.first, pair.second));
|
||||||
(view, pages) -> {},
|
|
||||||
(view, error) -> Timber.e("An error occurred while preloading a chapter"));
|
|
||||||
|
|
||||||
restartableLatestCache(GET_PAGE_IMAGES,
|
startable(PRELOAD_NEXT_CHAPTER, this::getPreloadNextChapterObservable,
|
||||||
this::getPageImagesObservable,
|
next -> {},
|
||||||
(view, page) -> {},
|
error -> Timber.e("Error preloading chapter"));
|
||||||
(view, error) -> Timber.e("An error occurred while downloading an image"));
|
|
||||||
|
|
||||||
restartableLatestCache(GET_ADJACENT_CHAPTERS,
|
|
||||||
this::getAdjacentChaptersObservable,
|
|
||||||
(view, pair) -> view.onAdjacentChapters(pair.first, pair.second),
|
|
||||||
(view, error) -> Timber.e("An error occurred while getting adjacent chapters"));
|
|
||||||
|
|
||||||
restartableLatestCache(RETRY_IMAGES,
|
restartable(GET_MANGA_SYNC, () -> getMangaSyncObservable().subscribe());
|
||||||
this::getRetryPageObservable,
|
|
||||||
(view, page) -> {},
|
|
||||||
(view, error) -> Timber.e("An error occurred while downloading an image"));
|
|
||||||
|
|
||||||
restartableLatestCache(GET_PAGE_LIST,
|
restartableLatestCache(GET_PAGE_LIST,
|
||||||
() -> getPageListObservable()
|
() -> getPageListObservable(activeChapter),
|
||||||
.doOnNext(pages -> pageList = pages)
|
(view, chapter) -> view.onChapterReady(manga, activeChapter, currentPage),
|
||||||
.doOnCompleted(() -> {
|
|
||||||
start(GET_ADJACENT_CHAPTERS);
|
|
||||||
start(GET_PAGE_IMAGES);
|
|
||||||
start(RETRY_IMAGES);
|
|
||||||
}),
|
|
||||||
(view, pages) -> view.onChapterReady(pages, manga, chapter, currentPage),
|
|
||||||
(view, error) -> view.onChapterError());
|
(view, error) -> view.onChapterError());
|
||||||
|
|
||||||
restartableFirst(GET_MANGA_SYNC, this::getMangaSyncObservable,
|
if (savedState == null) {
|
||||||
(view, mangaSync) -> {},
|
registerForEvents();
|
||||||
(view, error) -> {});
|
}
|
||||||
|
|
||||||
registerForStickyEvents();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -121,59 +108,85 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
super.onSave(state);
|
super.onSave(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onProcessRestart() {
|
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||||
source = sourceManager.get(sourceId);
|
public void onEvent(ReaderEvent event) {
|
||||||
|
|
||||||
// These are started by GET_PAGE_LIST, so we don't let them restart itselves
|
|
||||||
stop(GET_PAGE_IMAGES);
|
|
||||||
stop(GET_ADJACENT_CHAPTERS);
|
|
||||||
stop(RETRY_IMAGES);
|
|
||||||
stop(PRELOAD_NEXT_CHAPTER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@EventBusHook
|
|
||||||
public void onEventMainThread(ReaderEvent event) {
|
|
||||||
EventBus.getDefault().removeStickyEvent(event);
|
EventBus.getDefault().removeStickyEvent(event);
|
||||||
manga = event.getManga();
|
manga = event.getManga();
|
||||||
source = event.getSource();
|
source = event.getSource();
|
||||||
sourceId = source.getId();
|
sourceId = source.getId();
|
||||||
|
initializeSubjects();
|
||||||
loadChapter(event.getChapter());
|
loadChapter(event.getChapter());
|
||||||
if (prefs.autoUpdateMangaSync()) {
|
if (prefs.autoUpdateMangaSync()) {
|
||||||
start(GET_MANGA_SYNC);
|
start(GET_MANGA_SYNC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initializeSubjects() {
|
||||||
|
// Listen for pages initialization events
|
||||||
|
pageInitializerSubject = PublishSubject.create();
|
||||||
|
add(pageInitializerSubject
|
||||||
|
.observeOn(Schedulers.io())
|
||||||
|
.concatMap(chapter -> {
|
||||||
|
Observable observable;
|
||||||
|
if (chapter.isDownloaded()) {
|
||||||
|
File chapterDir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter);
|
||||||
|
observable = Observable.from(chapter.getPages())
|
||||||
|
.flatMap(page -> downloadManager.getDownloadedImage(page, chapterDir));
|
||||||
|
} else {
|
||||||
|
observable = source.getAllImageUrlsFromPageList(chapter.getPages())
|
||||||
|
.flatMap(source::getCachedImage, 2)
|
||||||
|
.doOnCompleted(() -> source.savePageList(chapter.url, chapter.getPages()));
|
||||||
|
}
|
||||||
|
return observable.doOnCompleted(() -> {
|
||||||
|
if (!seamlessMode && activeChapter == chapter) {
|
||||||
|
preloadNextChapter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.subscribe());
|
||||||
|
|
||||||
|
// Listen por retry events
|
||||||
|
retryPageSubject = PublishSubject.create();
|
||||||
|
add(retryPageSubject
|
||||||
|
.observeOn(Schedulers.io())
|
||||||
|
.flatMap(page -> page.getImageUrl() == null ?
|
||||||
|
source.getImageUrlFromPage(page) :
|
||||||
|
Observable.just(page))
|
||||||
|
.flatMap(source::getCachedImage)
|
||||||
|
.subscribe());
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the page list of a chapter
|
// Returns the page list of a chapter
|
||||||
private Observable<List<Page>> getPageListObservable() {
|
private Observable<Chapter> getPageListObservable(Chapter chapter) {
|
||||||
return isDownloaded ?
|
return (chapter.isDownloaded() ?
|
||||||
// Fetch the page list from disk
|
// Fetch the page list from disk
|
||||||
Observable.just(downloadManager.getSavedPageList(source, manga, chapter)) :
|
Observable.just(downloadManager.getSavedPageList(source, manga, chapter)) :
|
||||||
// Fetch the page list from cache or fallback to network
|
// Fetch the page list from cache or fallback to network
|
||||||
source.getCachedPageListOrPullFromNetwork(chapter.url)
|
source.getCachedPageListOrPullFromNetwork(chapter.url)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread());
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
).map(pages -> {
|
||||||
|
for (Page page : pages) {
|
||||||
|
page.setChapter(chapter);
|
||||||
}
|
}
|
||||||
|
chapter.setPages(pages);
|
||||||
// Get the chapter images from network or disk
|
if (requestedPage >= -1 || currentPage == null) {
|
||||||
private Observable<Page> getPageImagesObservable() {
|
if (requestedPage == -1) {
|
||||||
Observable<Page> pageObservable;
|
currentPage = pages.get(pages.size() - 1);
|
||||||
|
|
||||||
if (!isDownloaded) {
|
|
||||||
pageObservable = source.getAllImageUrlsFromPageList(pageList)
|
|
||||||
.flatMap(source::getCachedImage, 2);
|
|
||||||
} else {
|
} else {
|
||||||
File chapterDir = downloadManager.getAbsoluteChapterDirectory(source, manga, chapter);
|
currentPage = pages.get(requestedPage);
|
||||||
pageObservable = Observable.from(pageList)
|
|
||||||
.flatMap(page -> downloadManager.getDownloadedImage(page, chapterDir));
|
|
||||||
}
|
}
|
||||||
return pageObservable.subscribeOn(Schedulers.io())
|
}
|
||||||
.doOnCompleted(this::preloadNextChapter);
|
requestedPage = -2;
|
||||||
|
pageInitializerSubject.onNext(chapter);
|
||||||
|
return chapter;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Observable<Pair<Chapter, Chapter>> getAdjacentChaptersObservable() {
|
private Observable<Pair<Chapter, Chapter>> getAdjacentChaptersObservable() {
|
||||||
return Observable.zip(
|
return Observable.zip(
|
||||||
db.getPreviousChapter(chapter).asRxObservable().take(1),
|
db.getPreviousChapter(activeChapter).asRxObservable().take(1),
|
||||||
db.getNextChapter(chapter).asRxObservable().take(1),
|
db.getNextChapter(activeChapter).asRxObservable().take(1),
|
||||||
Pair::create)
|
Pair::create)
|
||||||
.doOnNext(pair -> {
|
.doOnNext(pair -> {
|
||||||
previousChapter = pair.first;
|
previousChapter = pair.first;
|
||||||
@ -182,29 +195,22 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
.observeOn(AndroidSchedulers.mainThread());
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for retry page events
|
// Preload the first pages of the next chapter. Only for non seamless mode
|
||||||
private Observable<Page> getRetryPageObservable() {
|
|
||||||
return retryPageSubject
|
|
||||||
.observeOn(Schedulers.io())
|
|
||||||
.flatMap(page -> page.getImageUrl() == null ?
|
|
||||||
source.getImageUrlFromPage(page) :
|
|
||||||
Observable.just(page))
|
|
||||||
.flatMap(source::getCachedImage)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preload the first pages of the next chapter
|
|
||||||
private Observable<Page> getPreloadNextChapterObservable() {
|
private Observable<Page> getPreloadNextChapterObservable() {
|
||||||
return source.getCachedPageListOrPullFromNetwork(nextChapter.url)
|
return source.getCachedPageListOrPullFromNetwork(nextChapter.url)
|
||||||
.flatMap(pages -> {
|
.flatMap(pages -> {
|
||||||
nextChapterPageList = pages;
|
nextChapter.setPages(pages);
|
||||||
// Preload at most 5 pages
|
|
||||||
int pagesToPreload = Math.min(pages.size(), 5);
|
int pagesToPreload = Math.min(pages.size(), 5);
|
||||||
return Observable.from(pages).take(pagesToPreload);
|
return Observable.from(pages).take(pagesToPreload);
|
||||||
})
|
})
|
||||||
|
// Preload up to 5 images
|
||||||
.concatMap(page -> page.getImageUrl() == null ?
|
.concatMap(page -> page.getImageUrl() == null ?
|
||||||
source.getImageUrlFromPage(page) :
|
source.getImageUrlFromPage(page) :
|
||||||
Observable.just(page))
|
Observable.just(page))
|
||||||
|
// Download the first image
|
||||||
|
.concatMap(page -> page.getPageNumber() == 0 ?
|
||||||
|
source.getCachedImage(page) :
|
||||||
|
Observable.just(page))
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.doOnCompleted(this::stopPreloadingNextChapter);
|
.doOnCompleted(this::stopPreloadingNextChapter);
|
||||||
@ -212,6 +218,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
|
|
||||||
private Observable<List<MangaSync>> getMangaSyncObservable() {
|
private Observable<List<MangaSync>> getMangaSyncObservable() {
|
||||||
return db.getMangasSync(manga).asRxObservable()
|
return db.getMangasSync(manga).asRxObservable()
|
||||||
|
.take(1)
|
||||||
.doOnNext(mangaSync -> this.mangaSyncList = mangaSync);
|
.doOnNext(mangaSync -> this.mangaSyncList = mangaSync);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,24 +228,58 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
|
|
||||||
// Loads the given chapter
|
// Loads the given chapter
|
||||||
private void loadChapter(Chapter chapter, int requestedPage) {
|
private void loadChapter(Chapter chapter, int requestedPage) {
|
||||||
// Before loading the chapter, stop preloading (if it's working) and save current progress
|
if (seamlessMode) {
|
||||||
|
if (appenderSubscription != null)
|
||||||
|
remove(appenderSubscription);
|
||||||
|
} else {
|
||||||
stopPreloadingNextChapter();
|
stopPreloadingNextChapter();
|
||||||
|
}
|
||||||
|
|
||||||
this.chapter = chapter;
|
this.activeChapter = chapter;
|
||||||
isDownloaded = isChapterDownloaded(chapter);
|
chapter.status = isChapterDownloaded(chapter) ? Download.DOWNLOADED : Download.NOT_DOWNLOADED;
|
||||||
|
|
||||||
// If the chapter is partially read, set the starting page to the last the user read
|
// If the chapter is partially read, set the starting page to the last the user read
|
||||||
if (!chapter.read && chapter.last_page_read != 0)
|
if (!chapter.read && chapter.last_page_read != 0)
|
||||||
currentPage = chapter.last_page_read;
|
this.requestedPage = chapter.last_page_read;
|
||||||
else
|
else
|
||||||
currentPage = requestedPage;
|
this.requestedPage = requestedPage;
|
||||||
|
|
||||||
// Reset next and previous chapter. They have to be fetched again
|
// Reset next and previous chapter. They have to be fetched again
|
||||||
nextChapter = null;
|
nextChapter = null;
|
||||||
previousChapter = null;
|
previousChapter = null;
|
||||||
nextChapterPageList = null;
|
|
||||||
|
|
||||||
start(GET_PAGE_LIST);
|
start(GET_PAGE_LIST);
|
||||||
|
start(GET_ADJACENT_CHAPTERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActiveChapter(Chapter chapter) {
|
||||||
|
onChapterLeft();
|
||||||
|
this.activeChapter = chapter;
|
||||||
|
nextChapter = null;
|
||||||
|
previousChapter = null;
|
||||||
|
start(GET_ADJACENT_CHAPTERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void appendNextChapter() {
|
||||||
|
if (nextChapter == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (appenderSubscription != null)
|
||||||
|
remove(appenderSubscription);
|
||||||
|
|
||||||
|
nextChapter.status = isChapterDownloaded(nextChapter) ? Download.DOWNLOADED : Download.NOT_DOWNLOADED;
|
||||||
|
|
||||||
|
appenderSubscription = getPageListObservable(nextChapter)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.compose(deliverLatestCache())
|
||||||
|
.subscribe(split((view, chapter) -> {
|
||||||
|
view.onAppendChapter(chapter);
|
||||||
|
}, (view, error) -> {
|
||||||
|
view.onChapterAppendError();
|
||||||
|
}));
|
||||||
|
|
||||||
|
add(appenderSubscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether the given chapter is downloaded
|
// Check whether the given chapter is downloaded
|
||||||
@ -254,34 +295,39 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
// Called before loading another chapter or leaving the reader. It allows to do operations
|
// Called before loading another chapter or leaving the reader. It allows to do operations
|
||||||
// over the chapter read like saving progress
|
// over the chapter read like saving progress
|
||||||
public void onChapterLeft() {
|
public void onChapterLeft() {
|
||||||
if (pageList == null)
|
List<Page> pages = activeChapter.getPages();
|
||||||
|
if (pages == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Get the last page read
|
||||||
|
int activePageNumber = activeChapter.last_page_read;
|
||||||
|
|
||||||
|
// Just in case, avoid out of index exceptions
|
||||||
|
if (activePageNumber >= pages.size()) {
|
||||||
|
activePageNumber = pages.size() - 1;
|
||||||
|
}
|
||||||
|
Page activePage = pages.get(activePageNumber);
|
||||||
|
|
||||||
// Cache current page list progress for online chapters to allow a faster reopen
|
// Cache current page list progress for online chapters to allow a faster reopen
|
||||||
if (!isDownloaded)
|
if (!activeChapter.isDownloaded()) {
|
||||||
source.savePageList(chapter.url, pageList);
|
source.savePageList(activeChapter.url, pages);
|
||||||
|
}
|
||||||
|
|
||||||
// Save current progress of the chapter. Mark as read if the chapter is finished
|
// Save current progress of the chapter. Mark as read if the chapter is finished
|
||||||
chapter.last_page_read = currentPage;
|
if (activePage.isLastPage()) {
|
||||||
if (isChapterFinished()) {
|
activeChapter.read = true;
|
||||||
chapter.read = true;
|
|
||||||
}
|
}
|
||||||
db.insertChapter(chapter).asRxObservable().subscribe();
|
db.insertChapter(activeChapter).asRxObservable().subscribe();
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether the chapter has been read
|
|
||||||
private boolean isChapterFinished() {
|
|
||||||
return !chapter.read && currentPage == pageList.size() - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMangaSyncChapterToUpdate() {
|
public int getMangaSyncChapterToUpdate() {
|
||||||
if (pageList == null || mangaSyncList == null || mangaSyncList.isEmpty())
|
if (activeChapter.getPages() == null || mangaSyncList == null || mangaSyncList.isEmpty())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
int lastChapterReadLocal = 0;
|
int lastChapterReadLocal = 0;
|
||||||
// If the current chapter has been read, we check with this one
|
// If the current chapter has been read, we check with this one
|
||||||
if (chapter.read)
|
if (activeChapter.read)
|
||||||
lastChapterReadLocal = (int) Math.floor(chapter.chapter_number);
|
lastChapterReadLocal = (int) Math.floor(activeChapter.chapter_number);
|
||||||
// If not, we check if the previous chapter has been read
|
// If not, we check if the previous chapter has been read
|
||||||
else if (previousChapter != null && previousChapter.read)
|
else if (previousChapter != null && previousChapter.read)
|
||||||
lastChapterReadLocal = (int) Math.floor(previousChapter.chapter_number);
|
lastChapterReadLocal = (int) Math.floor(previousChapter.chapter_number);
|
||||||
@ -309,7 +355,7 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentPage(int currentPage) {
|
public void setCurrentPage(Page currentPage) {
|
||||||
this.currentPage = currentPage;
|
this.currentPage = currentPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,8 +394,8 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
private void stopPreloadingNextChapter() {
|
private void stopPreloadingNextChapter() {
|
||||||
if (!isUnsubscribed(PRELOAD_NEXT_CHAPTER)) {
|
if (!isUnsubscribed(PRELOAD_NEXT_CHAPTER)) {
|
||||||
stop(PRELOAD_NEXT_CHAPTER);
|
stop(PRELOAD_NEXT_CHAPTER);
|
||||||
if (nextChapterPageList != null)
|
if (nextChapter.getPages() != null)
|
||||||
source.savePageList(nextChapter.url, nextChapterPageList);
|
source.savePageList(nextChapter.url, nextChapter.getPages());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,4 +408,11 @@ public class ReaderPresenter extends BasePresenter<ReaderActivity> {
|
|||||||
return manga;
|
return manga;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Page getCurrentPage() {
|
||||||
|
return currentPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSeamlessMode() {
|
||||||
|
return seamlessMode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.base;
|
package eu.kanade.tachiyomi.ui.reader.viewer.base;
|
||||||
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
|
|
||||||
import com.davemorrissey.labs.subscaleview.decoder.ImageDecoder;
|
import com.davemorrissey.labs.subscaleview.decoder.ImageDecoder;
|
||||||
import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder;
|
import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder;
|
||||||
import com.davemorrissey.labs.subscaleview.decoder.RapidImageRegionDecoder;
|
import com.davemorrissey.labs.subscaleview.decoder.RapidImageRegionDecoder;
|
||||||
import com.davemorrissey.labs.subscaleview.decoder.SkiaImageDecoder;
|
import com.davemorrissey.labs.subscaleview.decoder.SkiaImageDecoder;
|
||||||
import com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder;
|
import com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
import eu.kanade.tachiyomi.data.source.model.Page;
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment;
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment;
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||||
@ -18,40 +18,86 @@ public abstract class BaseReader extends BaseFragment {
|
|||||||
|
|
||||||
protected int currentPage;
|
protected int currentPage;
|
||||||
protected List<Page> pages;
|
protected List<Page> pages;
|
||||||
|
protected List<Chapter> chapters;
|
||||||
protected Class<? extends ImageRegionDecoder> regionDecoderClass;
|
protected Class<? extends ImageRegionDecoder> regionDecoderClass;
|
||||||
protected Class<? extends ImageDecoder> bitmapDecoderClass;
|
protected Class<? extends ImageDecoder> bitmapDecoderClass;
|
||||||
|
|
||||||
|
private boolean hasRequestedNextChapter;
|
||||||
|
|
||||||
public static final int RAPID_DECODER = 0;
|
public static final int RAPID_DECODER = 0;
|
||||||
public static final int SKIA_DECODER = 1;
|
public static final int SKIA_DECODER = 1;
|
||||||
|
|
||||||
public void updatePageNumber() {
|
public void updatePageNumber() {
|
||||||
getReaderActivity().onPageChanged(getCurrentPage(), getTotalPages());
|
getReaderActivity().onPageChanged(getCurrentPage().getPageNumber(), getCurrentPage().getChapter().getPages().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCurrentPage() {
|
public Page getCurrentPage() {
|
||||||
return currentPage;
|
return pages.get(currentPage);
|
||||||
}
|
|
||||||
|
|
||||||
public int getPageForPosition(int position) {
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPositionForPage(int page) {
|
|
||||||
return page;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onPageChanged(int position) {
|
public void onPageChanged(int position) {
|
||||||
currentPage = getPageForPosition(position);
|
Page oldPage = pages.get(currentPage);
|
||||||
|
Page newPage = pages.get(position);
|
||||||
|
newPage.getChapter().last_page_read = newPage.getPageNumber();
|
||||||
|
|
||||||
|
if (getReaderActivity().getPresenter().isSeamlessMode()) {
|
||||||
|
Chapter oldChapter = oldPage.getChapter();
|
||||||
|
Chapter newChapter = newPage.getChapter();
|
||||||
|
if (!hasRequestedNextChapter && position > pages.size() - 5) {
|
||||||
|
hasRequestedNextChapter = true;
|
||||||
|
getReaderActivity().getPresenter().appendNextChapter();
|
||||||
|
}
|
||||||
|
if (!oldChapter.id.equals(newChapter.id)) {
|
||||||
|
onChapterChanged(newPage.getChapter(), newPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentPage = position;
|
||||||
updatePageNumber();
|
updatePageNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTotalPages() {
|
private void onChapterChanged(Chapter chapter, Page currentPage) {
|
||||||
return pages == null ? 0 : pages.size();
|
getReaderActivity().onEnterChapter(chapter, currentPage.getPageNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedPage(Page page) {
|
||||||
|
setSelectedPage(getPageIndex(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPageIndex(Page search) {
|
||||||
|
// search for the index of a page in the current list without requiring them to be the same object
|
||||||
|
for (Page page : pages) {
|
||||||
|
if (page.getPageNumber() == search.getPageNumber() &&
|
||||||
|
page.getChapter().id.equals(search.getChapter().id)) {
|
||||||
|
return pages.indexOf(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPageListReady(Chapter chapter, Page currentPage) {
|
||||||
|
if (chapters == null || !chapters.contains(chapter)) {
|
||||||
|
// if we reset the loaded page we also need to reset the loaded chapters
|
||||||
|
chapters = new ArrayList<>();
|
||||||
|
chapters.add(chapter);
|
||||||
|
onSetChapter(chapter, currentPage);
|
||||||
|
} else {
|
||||||
|
setSelectedPage(currentPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPageListAppendReady(Chapter chapter) {
|
||||||
|
if (!chapters.contains(chapter)) {
|
||||||
|
hasRequestedNextChapter = false;
|
||||||
|
chapters.add(chapter);
|
||||||
|
onAppendChapter(chapter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void setSelectedPage(int pageNumber);
|
public abstract void setSelectedPage(int pageNumber);
|
||||||
public abstract void onPageListReady(List<Page> pages, int currentPage);
|
public abstract void onSetChapter(Chapter chapter, Page currentPage);
|
||||||
public abstract boolean onImageTouch(MotionEvent motionEvent);
|
public abstract void onAppendChapter(Chapter chapter);
|
||||||
|
public abstract void moveToNext();
|
||||||
|
public abstract void moveToPrevious();
|
||||||
|
|
||||||
public void setDecoderClass(int value) {
|
public void setDecoderClass(int value) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.base;
|
|
||||||
|
|
||||||
public interface OnChapterSingleTapListener {
|
|
||||||
void onCenterTap();
|
|
||||||
void onLeftSideTap();
|
|
||||||
void onRightSideTap();
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.base;
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager;
|
||||||
|
|
||||||
public interface OnChapterBoundariesOutListener {
|
public interface OnChapterBoundariesOutListener {
|
||||||
void onFirstPageOutEvent();
|
void onFirstPageOutEvent();
|
@ -1,11 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager;
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager;
|
||||||
|
|
||||||
import android.support.v4.view.PagerAdapter;
|
import android.support.v4.view.PagerAdapter;
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterBoundariesOutListener;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterSingleTapListener;
|
|
||||||
import rx.functions.Action1;
|
import rx.functions.Action1;
|
||||||
|
|
||||||
public interface Pager {
|
public interface Pager {
|
||||||
@ -24,13 +21,7 @@ public interface Pager {
|
|||||||
PagerAdapter getAdapter();
|
PagerAdapter getAdapter();
|
||||||
void setAdapter(PagerAdapter adapter);
|
void setAdapter(PagerAdapter adapter);
|
||||||
|
|
||||||
boolean onImageTouch(MotionEvent motionEvent);
|
|
||||||
|
|
||||||
void setOnChapterBoundariesOutListener(OnChapterBoundariesOutListener listener);
|
void setOnChapterBoundariesOutListener(OnChapterBoundariesOutListener listener);
|
||||||
void setOnChapterSingleTapListener(OnChapterSingleTapListener listener);
|
|
||||||
|
|
||||||
OnChapterBoundariesOutListener getChapterBoundariesListener();
|
|
||||||
OnChapterSingleTapListener getChapterSingleTapListener();
|
|
||||||
|
|
||||||
void setOnPageChangeListener(Action1<Integer> onPageChanged);
|
void setOnPageChangeListener(Action1<Integer> onPageChanged);
|
||||||
void clearOnPageChangeListeners();
|
void clearOnPageChangeListeners();
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager;
|
|
||||||
|
|
||||||
import android.view.GestureDetector;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
|
|
||||||
public class PagerGestureListener extends GestureDetector.SimpleOnGestureListener {
|
|
||||||
|
|
||||||
private Pager pager;
|
|
||||||
|
|
||||||
private static final float LEFT_REGION = 0.33f;
|
|
||||||
private static final float RIGHT_REGION = 0.66f;
|
|
||||||
|
|
||||||
public PagerGestureListener(Pager pager) {
|
|
||||||
this.pager = pager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
|
||||||
final int position = pager.getCurrentItem();
|
|
||||||
final float positionX = e.getX();
|
|
||||||
|
|
||||||
if (positionX < pager.getWidth() * LEFT_REGION) {
|
|
||||||
if (position != 0) {
|
|
||||||
onLeftSideTap();
|
|
||||||
} else {
|
|
||||||
onFirstPageOut();
|
|
||||||
}
|
|
||||||
} else if (positionX > pager.getWidth() * RIGHT_REGION) {
|
|
||||||
if (position != pager.getAdapter().getCount() - 1) {
|
|
||||||
onRightSideTap();
|
|
||||||
} else {
|
|
||||||
onLastPageOut();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onCenterTap();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onLeftSideTap() {
|
|
||||||
if (pager.getChapterSingleTapListener() != null) {
|
|
||||||
pager.getChapterSingleTapListener().onLeftSideTap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onRightSideTap() {
|
|
||||||
if (pager.getChapterSingleTapListener() != null) {
|
|
||||||
pager.getChapterSingleTapListener().onRightSideTap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onCenterTap() {
|
|
||||||
if (pager.getChapterSingleTapListener() != null) {
|
|
||||||
pager.getChapterSingleTapListener().onCenterTap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onFirstPageOut() {
|
|
||||||
if (pager.getChapterBoundariesListener() != null) {
|
|
||||||
pager.getChapterBoundariesListener().onFirstPageOutEvent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onLastPageOut() {
|
|
||||||
if (pager.getChapterBoundariesListener() != null) {
|
|
||||||
pager.getChapterBoundariesListener().onLastPageOutEvent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,15 +1,18 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager;
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager;
|
||||||
|
|
||||||
|
import android.view.GestureDetector;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.R;
|
import eu.kanade.tachiyomi.R;
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
import eu.kanade.tachiyomi.data.source.model.Page;
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader;
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader;
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterBoundariesOutListener;
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader;
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterSingleTapListener;
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader;
|
||||||
import rx.subscriptions.CompositeSubscription;
|
import rx.subscriptions.CompositeSubscription;
|
||||||
|
|
||||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||||
@ -18,12 +21,21 @@ public abstract class PagerReader extends BaseReader {
|
|||||||
|
|
||||||
protected PagerReaderAdapter adapter;
|
protected PagerReaderAdapter adapter;
|
||||||
protected Pager pager;
|
protected Pager pager;
|
||||||
|
protected GestureDetector gestureDetector;
|
||||||
|
|
||||||
private boolean isReady;
|
|
||||||
protected boolean transitions;
|
protected boolean transitions;
|
||||||
protected CompositeSubscription subscriptions;
|
protected CompositeSubscription subscriptions;
|
||||||
|
|
||||||
protected int scaleType = 1;
|
protected int scaleType = 1;
|
||||||
|
protected int zoomStart = 1;
|
||||||
|
|
||||||
|
public static final int ALIGN_AUTO = 1;
|
||||||
|
public static final int ALIGN_LEFT = 2;
|
||||||
|
public static final int ALIGN_RIGHT = 3;
|
||||||
|
public static final int ALIGN_CENTER = 4;
|
||||||
|
|
||||||
|
private static final float LEFT_REGION = 0.33f;
|
||||||
|
private static final float RIGHT_REGION = 0.66f;
|
||||||
|
|
||||||
protected void initializePager(Pager pager) {
|
protected void initializePager(Pager pager) {
|
||||||
this.pager = pager;
|
this.pager = pager;
|
||||||
@ -33,55 +45,47 @@ public abstract class PagerReader extends BaseReader {
|
|||||||
pager.setOnChapterBoundariesOutListener(new OnChapterBoundariesOutListener() {
|
pager.setOnChapterBoundariesOutListener(new OnChapterBoundariesOutListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onFirstPageOutEvent() {
|
public void onFirstPageOutEvent() {
|
||||||
onFirstPageOut();
|
getReaderActivity().requestPreviousChapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLastPageOutEvent() {
|
public void onLastPageOutEvent() {
|
||||||
onLastPageOut();
|
getReaderActivity().requestNextChapter();
|
||||||
}
|
|
||||||
});
|
|
||||||
pager.setOnChapterSingleTapListener(new OnChapterSingleTapListener() {
|
|
||||||
@Override
|
|
||||||
public void onCenterTap() {
|
|
||||||
getReaderActivity().onCenterSingleTap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLeftSideTap() {
|
|
||||||
pager.setCurrentItem(pager.getCurrentItem() - 1, transitions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRightSideTap() {
|
|
||||||
pager.setCurrentItem(pager.getCurrentItem() + 1, transitions);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
gestureDetector = createGestureDetector();
|
||||||
|
|
||||||
adapter = new PagerReaderAdapter(getChildFragmentManager());
|
adapter = new PagerReaderAdapter(getChildFragmentManager());
|
||||||
pager.setAdapter(adapter);
|
pager.setAdapter(adapter);
|
||||||
|
|
||||||
|
PreferencesHelper preferences = getReaderActivity().getPreferences();
|
||||||
subscriptions = new CompositeSubscription();
|
subscriptions = new CompositeSubscription();
|
||||||
subscriptions.add(getReaderActivity().getPreferences().imageDecoder()
|
subscriptions.add(preferences.imageDecoder()
|
||||||
.asObservable()
|
.asObservable()
|
||||||
.doOnNext(this::setDecoderClass)
|
.doOnNext(this::setDecoderClass)
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.subscribe(v -> adapter.notifyDataSetChanged()));
|
.subscribe(v -> pager.setAdapter(adapter)));
|
||||||
|
|
||||||
subscriptions.add(getReaderActivity().getPreferences().imageScaleType()
|
subscriptions.add(preferences.imageScaleType()
|
||||||
.asObservable()
|
.asObservable()
|
||||||
.doOnNext(this::setImageScaleType)
|
.doOnNext(this::setImageScaleType)
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.subscribe(v -> adapter.notifyDataSetChanged()));
|
.subscribe(v -> pager.setAdapter(adapter)));
|
||||||
|
|
||||||
subscriptions.add(getReaderActivity().getPreferences().enableTransitions()
|
subscriptions.add(preferences.zoomStart()
|
||||||
|
.asObservable()
|
||||||
|
.doOnNext(this::setZoomStart)
|
||||||
|
.skip(1)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.subscribe(v -> pager.setAdapter(adapter)));
|
||||||
|
|
||||||
|
subscriptions.add(preferences.enableTransitions()
|
||||||
.asObservable()
|
.asObservable()
|
||||||
.subscribe(value -> transitions = value));
|
.subscribe(value -> transitions = value));
|
||||||
|
|
||||||
setPages();
|
setPages();
|
||||||
isReady = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -90,15 +94,42 @@ public abstract class PagerReader extends BaseReader {
|
|||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected GestureDetector createGestureDetector() {
|
||||||
|
return new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onPageListReady(List<Page> pages, int currentPage) {
|
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||||
if (this.pages != pages) {
|
final float positionX = e.getX();
|
||||||
this.pages = pages;
|
|
||||||
this.currentPage = currentPage;
|
if (positionX < pager.getWidth() * LEFT_REGION) {
|
||||||
if (isReady) {
|
onLeftSideTap();
|
||||||
|
} else if (positionX > pager.getWidth() * RIGHT_REGION) {
|
||||||
|
onRightSideTap();
|
||||||
|
} else {
|
||||||
|
getReaderActivity().onCenterSingleTap();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetChapter(Chapter chapter, Page currentPage) {
|
||||||
|
pages = new ArrayList<>(chapter.getPages());
|
||||||
|
this.currentPage = getPageIndex(currentPage); // we might have a new page object
|
||||||
|
|
||||||
|
// This method can be called before the view is created
|
||||||
|
if (pager != null) {
|
||||||
setPages();
|
setPages();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onAppendChapter(Chapter chapter) {
|
||||||
|
pages.addAll(chapter.getPages());
|
||||||
|
|
||||||
|
// This method can be called before the view is created
|
||||||
|
if (pager != null) {
|
||||||
|
adapter.setPages(pages);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setPages() {
|
protected void setPages() {
|
||||||
@ -113,19 +144,48 @@ public abstract class PagerReader extends BaseReader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSelectedPage(int pageNumber) {
|
public void setSelectedPage(int pageNumber) {
|
||||||
pager.setCurrentItem(getPositionForPage(pageNumber), false);
|
pager.setCurrentItem(pageNumber, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected void onLeftSideTap() {
|
||||||
public boolean onImageTouch(MotionEvent motionEvent) {
|
moveToPrevious();
|
||||||
return pager.onImageTouch(motionEvent);
|
}
|
||||||
|
|
||||||
|
protected void onRightSideTap() {
|
||||||
|
moveToNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveToNext() {
|
||||||
|
if (pager.getCurrentItem() != pager.getAdapter().getCount() - 1) {
|
||||||
|
pager.setCurrentItem(pager.getCurrentItem() + 1, transitions);
|
||||||
|
} else {
|
||||||
|
getReaderActivity().requestNextChapter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveToPrevious() {
|
||||||
|
if (pager.getCurrentItem() != 0) {
|
||||||
|
pager.setCurrentItem(pager.getCurrentItem() - 1, transitions);
|
||||||
|
} else {
|
||||||
|
getReaderActivity().requestPreviousChapter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setImageScaleType(int scaleType) {
|
private void setImageScaleType(int scaleType) {
|
||||||
this.scaleType = scaleType;
|
this.scaleType = scaleType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void onFirstPageOut();
|
private void setZoomStart(int zoomStart) {
|
||||||
public abstract void onLastPageOut();
|
if (zoomStart == ALIGN_AUTO) {
|
||||||
|
if (this instanceof LeftToRightReader)
|
||||||
|
setZoomStart(ALIGN_LEFT);
|
||||||
|
else if (this instanceof RightToLeftReader)
|
||||||
|
setZoomStart(ALIGN_RIGHT);
|
||||||
|
else
|
||||||
|
setZoomStart(ALIGN_CENTER);
|
||||||
|
} else {
|
||||||
|
this.zoomStart = zoomStart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,14 +31,10 @@ public class PagerReaderAdapter extends FragmentStatePagerAdapter {
|
|||||||
public Object instantiateItem(ViewGroup container, int position) {
|
public Object instantiateItem(ViewGroup container, int position) {
|
||||||
PagerReaderFragment f = (PagerReaderFragment) super.instantiateItem(container, position);
|
PagerReaderFragment f = (PagerReaderFragment) super.instantiateItem(container, position);
|
||||||
f.setPage(pages.get(position));
|
f.setPage(pages.get(position));
|
||||||
|
f.setPosition(position);
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemPosition(Object object) {
|
|
||||||
return POSITION_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Page> getPages() {
|
public List<Page> getPages() {
|
||||||
return pages;
|
return pages;
|
||||||
}
|
}
|
||||||
@ -48,4 +44,17 @@ public class PagerReaderAdapter extends FragmentStatePagerAdapter {
|
|||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemPosition(Object object) {
|
||||||
|
PagerReaderFragment f = (PagerReaderFragment) object;
|
||||||
|
int position = f.getPosition();
|
||||||
|
if (position >= 0 && position < getCount()) {
|
||||||
|
if (pages.get(position) == f.getPage()) {
|
||||||
|
return POSITION_UNCHANGED;
|
||||||
|
} else {
|
||||||
|
return POSITION_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.getItemPosition(object);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager;
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager;
|
||||||
|
|
||||||
|
import android.graphics.PointF;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
@ -16,6 +17,7 @@ import android.widget.TextView;
|
|||||||
import com.davemorrissey.labs.subscaleview.ImageSource;
|
import com.davemorrissey.labs.subscaleview.ImageSource;
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@ -25,6 +27,7 @@ import eu.kanade.tachiyomi.R;
|
|||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
import eu.kanade.tachiyomi.data.source.model.Page;
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment;
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment;
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader;
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical.VerticalReader;
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical.VerticalReader;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
import rx.Subscription;
|
import rx.Subscription;
|
||||||
@ -41,9 +44,12 @@ public class PagerReaderFragment extends BaseFragment {
|
|||||||
@Bind(R.id.retry_button) Button retryButton;
|
@Bind(R.id.retry_button) Button retryButton;
|
||||||
|
|
||||||
private Page page;
|
private Page page;
|
||||||
private boolean isReady;
|
|
||||||
private Subscription progressSubscription;
|
private Subscription progressSubscription;
|
||||||
private Subscription statusSubscription;
|
private Subscription statusSubscription;
|
||||||
|
private int position = -1;
|
||||||
|
|
||||||
|
private int lightGreyColor;
|
||||||
|
private int blackColor;
|
||||||
|
|
||||||
public static PagerReaderFragment newInstance() {
|
public static PagerReaderFragment newInstance() {
|
||||||
return new PagerReaderFragment();
|
return new PagerReaderFragment();
|
||||||
@ -56,8 +62,15 @@ public class PagerReaderFragment extends BaseFragment {
|
|||||||
ReaderActivity activity = getReaderActivity();
|
ReaderActivity activity = getReaderActivity();
|
||||||
PagerReader parentFragment = (PagerReader) getParentFragment();
|
PagerReader parentFragment = (PagerReader) getParentFragment();
|
||||||
|
|
||||||
|
lightGreyColor = ContextCompat.getColor(getContext(), R.color.light_grey);
|
||||||
|
blackColor = ContextCompat.getColor(getContext(), R.color.primary_text);
|
||||||
|
|
||||||
if (activity.getReaderTheme() == ReaderActivity.BLACK_THEME) {
|
if (activity.getReaderTheme() == ReaderActivity.BLACK_THEME) {
|
||||||
progressText.setTextColor(ContextCompat.getColor(getContext(), R.color.light_grey));
|
progressText.setTextColor(lightGreyColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentFragment instanceof RightToLeftReader) {
|
||||||
|
view.setRotation(-180);
|
||||||
}
|
}
|
||||||
|
|
||||||
imageView.setParallelLoadingEnabled(true);
|
imageView.setParallelLoadingEnabled(true);
|
||||||
@ -65,11 +78,29 @@ public class PagerReaderFragment extends BaseFragment {
|
|||||||
imageView.setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED);
|
imageView.setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED);
|
||||||
imageView.setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE);
|
imageView.setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE);
|
||||||
imageView.setMinimumScaleType(parentFragment.scaleType);
|
imageView.setMinimumScaleType(parentFragment.scaleType);
|
||||||
|
imageView.setMinimumDpi(50);
|
||||||
imageView.setRegionDecoderClass(parentFragment.getRegionDecoderClass());
|
imageView.setRegionDecoderClass(parentFragment.getRegionDecoderClass());
|
||||||
imageView.setBitmapDecoderClass(parentFragment.getBitmapDecoderClass());
|
imageView.setBitmapDecoderClass(parentFragment.getBitmapDecoderClass());
|
||||||
imageView.setVerticalScrollingParent(parentFragment instanceof VerticalReader);
|
imageView.setVerticalScrollingParent(parentFragment instanceof VerticalReader);
|
||||||
imageView.setOnTouchListener((v, motionEvent) -> parentFragment.onImageTouch(motionEvent));
|
imageView.setOnTouchListener((v, motionEvent) -> parentFragment.gestureDetector.onTouchEvent(motionEvent));
|
||||||
imageView.setOnImageEventListener(new SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
imageView.setOnImageEventListener(new SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
||||||
|
@Override
|
||||||
|
public void onReady() {
|
||||||
|
switch (parentFragment.zoomStart) {
|
||||||
|
case PagerReader.ALIGN_LEFT:
|
||||||
|
imageView.setScaleAndCenter(imageView.getScale(), new PointF(0, 0));
|
||||||
|
break;
|
||||||
|
case PagerReader.ALIGN_RIGHT:
|
||||||
|
imageView.setScaleAndCenter(imageView.getScale(), new PointF(imageView.getSWidth(), 0));
|
||||||
|
break;
|
||||||
|
case PagerReader.ALIGN_CENTER:
|
||||||
|
PointF center = imageView.getCenter();
|
||||||
|
center.y = 0;
|
||||||
|
imageView.setScaleAndCenter(imageView.getScale(), center);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onImageLoadError(Exception e) {
|
public void onImageLoadError(Exception e) {
|
||||||
showImageLoadError();
|
showImageLoadError();
|
||||||
@ -85,7 +116,6 @@ public class PagerReaderFragment extends BaseFragment {
|
|||||||
});
|
});
|
||||||
|
|
||||||
observeStatus();
|
observeStatus();
|
||||||
isReady = true;
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,23 +123,36 @@ public class PagerReaderFragment extends BaseFragment {
|
|||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
unsubscribeProgress();
|
unsubscribeProgress();
|
||||||
unsubscribeStatus();
|
unsubscribeStatus();
|
||||||
|
imageView.setOnTouchListener(null);
|
||||||
|
imageView.setOnImageEventListener(null);
|
||||||
ButterKnife.unbind(this);
|
ButterKnife.unbind(this);
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPage(Page page) {
|
public void setPage(Page page) {
|
||||||
this.page = page;
|
this.page = page;
|
||||||
if (isReady) {
|
|
||||||
|
// This method can be called before the view is created
|
||||||
|
if (imageView != null) {
|
||||||
observeStatus();
|
observeStatus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPosition(int position) {
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
private void showImage() {
|
private void showImage() {
|
||||||
if (page == null || page.getImagePath() == null)
|
if (page == null || page.getImagePath() == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
File imagePath = new File(page.getImagePath());
|
||||||
|
if (imagePath.exists()) {
|
||||||
imageView.setImage(ImageSource.uri(page.getImagePath()));
|
imageView.setImage(ImageSource.uri(page.getImagePath()));
|
||||||
progressContainer.setVisibility(View.GONE);
|
progressContainer.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
page.setStatus(Page.ERROR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showDownloading() {
|
private void showDownloading() {
|
||||||
@ -141,8 +184,7 @@ public class PagerReaderFragment extends BaseFragment {
|
|||||||
errorText.setGravity(Gravity.CENTER);
|
errorText.setGravity(Gravity.CENTER);
|
||||||
errorText.setText(R.string.decode_image_error);
|
errorText.setText(R.string.decode_image_error);
|
||||||
errorText.setTextColor(getReaderActivity().getReaderTheme() == ReaderActivity.BLACK_THEME ?
|
errorText.setTextColor(getReaderActivity().getReaderTheme() == ReaderActivity.BLACK_THEME ?
|
||||||
ContextCompat.getColor(getContext(), R.color.light_grey) :
|
lightGreyColor : blackColor);
|
||||||
ContextCompat.getColor(getContext(), R.color.primary_text));
|
|
||||||
|
|
||||||
view.addView(errorText);
|
view.addView(errorText);
|
||||||
}
|
}
|
||||||
@ -162,7 +204,6 @@ public class PagerReaderFragment extends BaseFragment {
|
|||||||
case Page.READY:
|
case Page.READY:
|
||||||
showImage();
|
showImage();
|
||||||
unsubscribeProgress();
|
unsubscribeProgress();
|
||||||
unsubscribeStatus();
|
|
||||||
break;
|
break;
|
||||||
case Page.ERROR:
|
case Page.ERROR:
|
||||||
showError();
|
showError();
|
||||||
@ -217,6 +258,14 @@ public class PagerReaderFragment extends BaseFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Page getPage() {
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
private ReaderActivity getReaderActivity() {
|
private ReaderActivity getReaderActivity() {
|
||||||
return (ReaderActivity) getActivity();
|
return (ReaderActivity) getActivity();
|
||||||
}
|
}
|
||||||
|
@ -2,38 +2,21 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.v4.view.ViewPager;
|
import android.support.v4.view.ViewPager;
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.GestureDetector;
|
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterBoundariesOutListener;
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.OnChapterBoundariesOutListener;
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterSingleTapListener;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager;
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager;
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerGestureListener;
|
|
||||||
import rx.functions.Action1;
|
import rx.functions.Action1;
|
||||||
|
|
||||||
public class HorizontalPager extends ViewPager implements Pager {
|
public class HorizontalPager extends ViewPager implements Pager {
|
||||||
|
|
||||||
private GestureDetector gestureDetector;
|
|
||||||
|
|
||||||
private OnChapterBoundariesOutListener onChapterBoundariesOutListener;
|
private OnChapterBoundariesOutListener onChapterBoundariesOutListener;
|
||||||
private OnChapterSingleTapListener onChapterSingleTapListener;
|
|
||||||
|
|
||||||
private static final float SWIPE_TOLERANCE = 0.25f;
|
private static final float SWIPE_TOLERANCE = 0.25f;
|
||||||
private float startDragX;
|
private float startDragX;
|
||||||
|
|
||||||
public HorizontalPager(Context context) {
|
public HorizontalPager(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
init(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HorizontalPager(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
init(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(Context context) {
|
|
||||||
gestureDetector = new GestureDetector(context, new PagerGestureListener(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -86,31 +69,11 @@ public class HorizontalPager extends ViewPager implements Pager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onImageTouch(MotionEvent event) {
|
|
||||||
return gestureDetector.onTouchEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setOnChapterBoundariesOutListener(OnChapterBoundariesOutListener listener) {
|
public void setOnChapterBoundariesOutListener(OnChapterBoundariesOutListener listener) {
|
||||||
onChapterBoundariesOutListener = listener;
|
onChapterBoundariesOutListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOnChapterSingleTapListener(OnChapterSingleTapListener listener) {
|
|
||||||
onChapterSingleTapListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OnChapterBoundariesOutListener getChapterBoundariesListener() {
|
|
||||||
return onChapterBoundariesOutListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OnChapterSingleTapListener getChapterSingleTapListener() {
|
|
||||||
return onChapterSingleTapListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setOnPageChangeListener(Action1<Integer> function) {
|
public void setOnPageChangeListener(Action1<Integer> function) {
|
||||||
addOnPageChangeListener(new SimpleOnPageChangeListener() {
|
addOnPageChangeListener(new SimpleOnPageChangeListener() {
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader;
|
|
||||||
|
|
||||||
public abstract class HorizontalReader extends PagerReader {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
|
|
||||||
HorizontalPager pager = new HorizontalPager(getActivity());
|
|
||||||
initializePager(pager);
|
|
||||||
return pager;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,15 +1,19 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal;
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal;
|
||||||
|
|
||||||
public class LeftToRightReader extends HorizontalReader {
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader;
|
||||||
|
|
||||||
|
public class LeftToRightReader extends PagerReader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFirstPageOut() {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
|
||||||
getReaderActivity().requestPreviousChapter();
|
HorizontalPager pager = new HorizontalPager(getActivity());
|
||||||
}
|
initializePager(pager);
|
||||||
|
return pager;
|
||||||
@Override
|
|
||||||
public void onLastPageOut() {
|
|
||||||
getReaderActivity().requestNextChapter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,30 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal;
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import android.os.Bundle;
|
||||||
import java.util.Collections;
|
import android.view.LayoutInflater;
|
||||||
import java.util.List;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerReader;
|
||||||
|
|
||||||
public class RightToLeftReader extends HorizontalReader {
|
public class RightToLeftReader extends PagerReader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPageListReady(List<Page> pages, int currentPage) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
|
||||||
ArrayList<Page> inversedPages = new ArrayList<>(pages);
|
HorizontalPager pager = new HorizontalPager(getActivity());
|
||||||
Collections.reverse(inversedPages);
|
pager.setRotation(180);
|
||||||
super.onPageListReady(inversedPages, currentPage);
|
initializePager(pager);
|
||||||
|
return pager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPageForPosition(int position) {
|
protected void onLeftSideTap() {
|
||||||
return (getTotalPages() - 1) - position;
|
moveToNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getPositionForPage(int page) {
|
protected void onRightSideTap() {
|
||||||
return (getTotalPages() - 1) - page;
|
moveToPrevious();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFirstPageOut() {
|
|
||||||
getReaderActivity().requestNextChapter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLastPageOut() {
|
|
||||||
getReaderActivity().requestPreviousChapter();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,21 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical;
|
package eu.kanade.tachiyomi.ui.reader.viewer.pager.vertical;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.GestureDetector;
|
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterBoundariesOutListener;
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.OnChapterBoundariesOutListener;
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.OnChapterSingleTapListener;
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager;
|
import eu.kanade.tachiyomi.ui.reader.viewer.pager.Pager;
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerGestureListener;
|
|
||||||
import rx.functions.Action1;
|
import rx.functions.Action1;
|
||||||
|
|
||||||
public class VerticalPager extends VerticalViewPagerImpl implements Pager {
|
public class VerticalPager extends VerticalViewPagerImpl implements Pager {
|
||||||
|
|
||||||
private GestureDetector gestureDetector;
|
|
||||||
|
|
||||||
private OnChapterBoundariesOutListener onChapterBoundariesOutListener;
|
private OnChapterBoundariesOutListener onChapterBoundariesOutListener;
|
||||||
private OnChapterSingleTapListener onChapterSingleTapListener;
|
|
||||||
|
|
||||||
private static final float SWIPE_TOLERANCE = 0.25f;
|
private static final float SWIPE_TOLERANCE = 0.25f;
|
||||||
private float startDragY;
|
private float startDragY;
|
||||||
|
|
||||||
public VerticalPager(Context context) {
|
public VerticalPager(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
init(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public VerticalPager(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
init(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(Context context) {
|
|
||||||
gestureDetector = new GestureDetector(context, new PagerGestureListener(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -85,31 +68,11 @@ public class VerticalPager extends VerticalViewPagerImpl implements Pager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onImageTouch(MotionEvent event) {
|
|
||||||
return gestureDetector.onTouchEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setOnChapterBoundariesOutListener(OnChapterBoundariesOutListener listener) {
|
public void setOnChapterBoundariesOutListener(OnChapterBoundariesOutListener listener) {
|
||||||
onChapterBoundariesOutListener = listener;
|
onChapterBoundariesOutListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOnChapterSingleTapListener(OnChapterSingleTapListener listener) {
|
|
||||||
onChapterSingleTapListener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OnChapterBoundariesOutListener getChapterBoundariesListener() {
|
|
||||||
return onChapterBoundariesOutListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OnChapterSingleTapListener getChapterSingleTapListener() {
|
|
||||||
return onChapterSingleTapListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setOnPageChangeListener(Action1<Integer> function) {
|
public void setOnPageChangeListener(Action1<Integer> function) {
|
||||||
addOnPageChangeListener(new SimpleOnPageChangeListener() {
|
addOnPageChangeListener(new SimpleOnPageChangeListener() {
|
||||||
|
@ -16,14 +16,4 @@ public class VerticalReader extends PagerReader {
|
|||||||
return pager;
|
return pager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFirstPageOut() {
|
|
||||||
getReaderActivity().requestPreviousChapter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLastPageOut() {
|
|
||||||
getReaderActivity().requestNextChapter();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ public class WebtoonAdapter extends RecyclerView.Adapter<WebtoonHolder> {
|
|||||||
public WebtoonAdapter(WebtoonReader fragment) {
|
public WebtoonAdapter(WebtoonReader fragment) {
|
||||||
this.fragment = fragment;
|
this.fragment = fragment;
|
||||||
pages = new ArrayList<>();
|
pages = new ArrayList<>();
|
||||||
touchListener = (v, event) -> fragment.onImageTouch(event);
|
touchListener = (v, event) -> fragment.gestureDetector.onTouchEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page getItem(int position) {
|
public Page getItem(int position) {
|
||||||
|
@ -10,6 +10,8 @@ import android.widget.ProgressBar;
|
|||||||
import com.davemorrissey.labs.subscaleview.ImageSource;
|
import com.davemorrissey.labs.subscaleview.ImageSource;
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import eu.kanade.tachiyomi.R;
|
import eu.kanade.tachiyomi.R;
|
||||||
@ -62,7 +64,6 @@ public class WebtoonHolder extends RecyclerView.ViewHolder {
|
|||||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
if (page != null)
|
if (page != null)
|
||||||
adapter.retryPage(page);
|
adapter.retryPage(page);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@ -99,7 +100,14 @@ public class WebtoonHolder extends RecyclerView.ViewHolder {
|
|||||||
setErrorButtonVisible(false);
|
setErrorButtonVisible(false);
|
||||||
setProgressVisible(false);
|
setProgressVisible(false);
|
||||||
setImageVisible(true);
|
setImageVisible(true);
|
||||||
|
|
||||||
|
File imagePath = new File(page.getImagePath());
|
||||||
|
if (imagePath.exists()) {
|
||||||
imageView.setImage(ImageSource.uri(page.getImagePath()));
|
imageView.setImage(ImageSource.uri(page.getImagePath()));
|
||||||
|
} else {
|
||||||
|
page.setStatus(Page.ERROR);
|
||||||
|
onError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onError() {
|
private void onError() {
|
||||||
|
@ -8,8 +8,9 @@ import android.view.MotionEvent;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
||||||
import eu.kanade.tachiyomi.data.source.model.Page;
|
import eu.kanade.tachiyomi.data.source.model.Page;
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader;
|
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader;
|
||||||
import eu.kanade.tachiyomi.widget.PreCachingLayoutManager;
|
import eu.kanade.tachiyomi.widget.PreCachingLayoutManager;
|
||||||
@ -27,12 +28,11 @@ public class WebtoonReader extends BaseReader {
|
|||||||
private PreCachingLayoutManager layoutManager;
|
private PreCachingLayoutManager layoutManager;
|
||||||
private Subscription subscription;
|
private Subscription subscription;
|
||||||
private Subscription decoderSubscription;
|
private Subscription decoderSubscription;
|
||||||
private GestureDetector gestureDetector;
|
protected GestureDetector gestureDetector;
|
||||||
|
|
||||||
private boolean isReady;
|
|
||||||
private int scrollDistance;
|
private int scrollDistance;
|
||||||
|
|
||||||
private static final String SCROLL_STATE = "scroll_state";
|
private static final String SAVED_POSITION = "saved_position";
|
||||||
|
|
||||||
private static final float LEFT_REGION = 0.33f;
|
private static final float LEFT_REGION = 0.33f;
|
||||||
private static final float RIGHT_REGION = 0.66f;
|
private static final float RIGHT_REGION = 0.66f;
|
||||||
@ -47,7 +47,7 @@ public class WebtoonReader extends BaseReader {
|
|||||||
layoutManager = new PreCachingLayoutManager(getActivity());
|
layoutManager = new PreCachingLayoutManager(getActivity());
|
||||||
layoutManager.setExtraLayoutSpace(screenHeight / 2);
|
layoutManager.setExtraLayoutSpace(screenHeight / 2);
|
||||||
if (savedState != null) {
|
if (savedState != null) {
|
||||||
layoutManager.onRestoreInstanceState(savedState.getParcelable(SCROLL_STATE));
|
layoutManager.scrollToPositionWithOffset(savedState.getInt(SAVED_POSITION), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
recycler = new RecyclerView(getActivity());
|
recycler = new RecyclerView(getActivity());
|
||||||
@ -69,9 +69,9 @@ public class WebtoonReader extends BaseReader {
|
|||||||
final float positionX = e.getX();
|
final float positionX = e.getX();
|
||||||
|
|
||||||
if (positionX < recycler.getWidth() * LEFT_REGION) {
|
if (positionX < recycler.getWidth() * LEFT_REGION) {
|
||||||
recycler.smoothScrollBy(0, -scrollDistance);
|
moveToPrevious();
|
||||||
} else if (positionX > recycler.getWidth() * RIGHT_REGION) {
|
} else if (positionX > recycler.getWidth() * RIGHT_REGION) {
|
||||||
recycler.smoothScrollBy(0, scrollDistance);
|
moveToNext();
|
||||||
} else {
|
} else {
|
||||||
getReaderActivity().onCenterSingleTap();
|
getReaderActivity().onCenterSingleTap();
|
||||||
}
|
}
|
||||||
@ -80,8 +80,6 @@ public class WebtoonReader extends BaseReader {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setPages();
|
setPages();
|
||||||
isReady = true;
|
|
||||||
|
|
||||||
return recycler;
|
return recycler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +98,9 @@ public class WebtoonReader extends BaseReader {
|
|||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putParcelable(SCROLL_STATE, layoutManager.onSaveInstanceState());
|
int savedPosition = pages != null ?
|
||||||
|
pages.get(layoutManager.findFirstVisibleItemPosition()).getPageNumber() : 0;
|
||||||
|
outState.putInt(SAVED_POSITION, savedPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void unsubscribeStatus() {
|
private void unsubscribeStatus() {
|
||||||
@ -110,19 +110,44 @@ public class WebtoonReader extends BaseReader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSelectedPage(int pageNumber) {
|
public void setSelectedPage(int pageNumber) {
|
||||||
recycler.scrollToPosition(getPositionForPage(pageNumber));
|
recycler.scrollToPosition(pageNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPageListReady(List<Page> pages, int currentPage) {
|
public void moveToNext() {
|
||||||
if (this.pages != pages) {
|
recycler.smoothScrollBy(0, scrollDistance);
|
||||||
this.pages = pages;
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void moveToPrevious() {
|
||||||
|
recycler.smoothScrollBy(0, -scrollDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetChapter(Chapter chapter, Page currentPage) {
|
||||||
|
pages = new ArrayList<>(chapter.getPages());
|
||||||
// Restoring current page is not supported. It's getting weird scrolling jumps
|
// Restoring current page is not supported. It's getting weird scrolling jumps
|
||||||
// this.currentPage = currentPage;
|
// this.currentPage = currentPage;
|
||||||
if (isReady) {
|
|
||||||
|
// This method can be called before the view is created
|
||||||
|
if (recycler != null) {
|
||||||
setPages();
|
setPages();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAppendChapter(Chapter chapter) {
|
||||||
|
int insertStart = pages.size();
|
||||||
|
pages.addAll(chapter.getPages());
|
||||||
|
|
||||||
|
// This method can be called before the view is created
|
||||||
|
if (recycler != null) {
|
||||||
|
adapter.setPages(pages);
|
||||||
|
adapter.notifyItemRangeInserted(insertStart, chapter.getPages().size());
|
||||||
|
if (subscription != null && subscription.isUnsubscribed()) {
|
||||||
|
observeStatus(insertStart);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPages() {
|
private void setPages() {
|
||||||
@ -141,22 +166,19 @@ public class WebtoonReader extends BaseReader {
|
|||||||
recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||||
super.onScrolled(recyclerView, dx, dy);
|
int page = layoutManager.findLastVisibleItemPosition();
|
||||||
|
if (page != currentPage) {
|
||||||
currentPage = layoutManager.findLastVisibleItemPosition();
|
onPageChanged(page);
|
||||||
updatePageNumber();
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onImageTouch(MotionEvent motionEvent) {
|
|
||||||
return gestureDetector.onTouchEvent(motionEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void observeStatus(int position) {
|
private void observeStatus(int position) {
|
||||||
if (position == pages.size())
|
if (position == pages.size()) {
|
||||||
|
unsubscribeStatus();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final Page page = pages.get(position);
|
final Page page = pages.get(position);
|
||||||
|
|
||||||
|
@ -16,13 +16,33 @@ import eu.davidea.flexibleadapter.FlexibleAdapter;
|
|||||||
import eu.kanade.tachiyomi.R;
|
import eu.kanade.tachiyomi.R;
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter;
|
import eu.kanade.tachiyomi.data.database.models.MangaChapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter of RecentChaptersHolder.
|
||||||
|
* Connection between Fragment and Holder
|
||||||
|
* Holder updates should be called from here.
|
||||||
|
*/
|
||||||
public class RecentChaptersAdapter extends FlexibleAdapter<RecyclerView.ViewHolder, Object> {
|
public class RecentChaptersAdapter extends FlexibleAdapter<RecyclerView.ViewHolder, Object> {
|
||||||
|
|
||||||
private RecentChaptersFragment fragment;
|
/**
|
||||||
|
* Fragment of RecentChaptersFragment
|
||||||
|
*/
|
||||||
|
private final RecentChaptersFragment fragment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the view type
|
||||||
|
*/
|
||||||
private static final int VIEW_TYPE_CHAPTER = 0;
|
private static final int VIEW_TYPE_CHAPTER = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the view type
|
||||||
|
*/
|
||||||
private static final int VIEW_TYPE_SECTION = 1;
|
private static final int VIEW_TYPE_SECTION = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param fragment fragment
|
||||||
|
*/
|
||||||
public RecentChaptersAdapter(RecentChaptersFragment fragment) {
|
public RecentChaptersAdapter(RecentChaptersFragment fragment) {
|
||||||
this.fragment = fragment;
|
this.fragment = fragment;
|
||||||
setHasStableIds(true);
|
setHasStableIds(true);
|
||||||
@ -37,6 +57,11 @@ public class RecentChaptersAdapter extends FlexibleAdapter<RecyclerView.ViewHold
|
|||||||
return item.hashCode();
|
return item.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update items
|
||||||
|
*
|
||||||
|
* @param items items
|
||||||
|
*/
|
||||||
public void setItems(List<Object> items) {
|
public void setItems(List<Object> items) {
|
||||||
mItems = items;
|
mItems = items;
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
@ -56,6 +81,8 @@ public class RecentChaptersAdapter extends FlexibleAdapter<RecyclerView.ViewHold
|
|||||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||||
View v;
|
View v;
|
||||||
|
|
||||||
|
// Check which view type and set correct values.
|
||||||
switch (viewType) {
|
switch (viewType) {
|
||||||
case VIEW_TYPE_CHAPTER:
|
case VIEW_TYPE_CHAPTER:
|
||||||
v = inflater.inflate(R.layout.item_recent_chapter, parent, false);
|
v = inflater.inflate(R.layout.item_recent_chapter, parent, false);
|
||||||
@ -69,6 +96,7 @@ public class RecentChaptersAdapter extends FlexibleAdapter<RecyclerView.ViewHold
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||||
|
// Check which view type and set correct values.
|
||||||
switch (holder.getItemViewType()) {
|
switch (holder.getItemViewType()) {
|
||||||
case VIEW_TYPE_CHAPTER:
|
case VIEW_TYPE_CHAPTER:
|
||||||
final MangaChapter chapter = (MangaChapter) getItem(position);
|
final MangaChapter chapter = (MangaChapter) getItem(position);
|
||||||
@ -84,6 +112,10 @@ public class RecentChaptersAdapter extends FlexibleAdapter<RecyclerView.ViewHold
|
|||||||
holder.itemView.setActivated(isSelected(position));
|
holder.itemView.setActivated(isSelected(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns fragment
|
||||||
|
* @return RecentChaptersFragment
|
||||||
|
*/
|
||||||
public RecentChaptersFragment getFragment() {
|
public RecentChaptersFragment getFragment() {
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.recent;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
@ -9,18 +10,32 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import eu.kanade.tachiyomi.R;
|
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.database.models.MangaChapter;
|
import eu.kanade.tachiyomi.data.database.models.MangaChapter;
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadService;
|
||||||
|
import eu.kanade.tachiyomi.data.download.model.Download;
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
|
||||||
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
|
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
|
||||||
import eu.kanade.tachiyomi.ui.decoration.DividerItemDecoration;
|
import eu.kanade.tachiyomi.ui.decoration.DividerItemDecoration;
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||||
import nucleus.factory.RequiresPresenter;
|
import nucleus.factory.RequiresPresenter;
|
||||||
|
import rx.Observable;
|
||||||
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
|
import rx.schedulers.Schedulers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment that shows recent chapters.
|
||||||
|
* Uses R.layout.fragment_recent_chapters.
|
||||||
|
* UI related actions should be called from here.
|
||||||
|
*/
|
||||||
@RequiresPresenter(RecentChaptersPresenter.class)
|
@RequiresPresenter(RecentChaptersPresenter.class)
|
||||||
public class RecentChaptersFragment extends BaseRxFragment<RecentChaptersPresenter> implements FlexibleViewHolder.OnListItemClickListener {
|
public class RecentChaptersFragment extends BaseRxFragment<RecentChaptersPresenter> implements FlexibleViewHolder.OnListItemClickListener {
|
||||||
|
|
||||||
@ -50,14 +65,21 @@ public class RecentChaptersFragment extends BaseRxFragment<RecentChaptersPresent
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate adapter with chapters
|
||||||
|
*
|
||||||
|
* @param chapters list of chapters
|
||||||
|
*/
|
||||||
public void onNextMangaChapters(List<Object> chapters) {
|
public void onNextMangaChapters(List<Object> chapters) {
|
||||||
adapter.setItems(chapters);
|
adapter.setItems(chapters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onListItemClick(int position) {
|
public boolean onListItemClick(int position) {
|
||||||
|
// Get item from position
|
||||||
Object item = adapter.getItem(position);
|
Object item = adapter.getItem(position);
|
||||||
if (item instanceof MangaChapter) {
|
if (item instanceof MangaChapter) {
|
||||||
|
// Open chapter in reader
|
||||||
openChapter((MangaChapter) item);
|
openChapter((MangaChapter) item);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -65,12 +87,114 @@ public class RecentChaptersFragment extends BaseRxFragment<RecentChaptersPresent
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onListItemLongClick(int position) {
|
public void onListItemLongClick(int position) {
|
||||||
|
// Empty function
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void openChapter(MangaChapter chapter) {
|
/**
|
||||||
|
* Open chapter in reader
|
||||||
|
*
|
||||||
|
* @param chapter selected chapter
|
||||||
|
*/
|
||||||
|
private void openChapter(MangaChapter chapter) {
|
||||||
getPresenter().onOpenChapter(chapter);
|
getPresenter().onOpenChapter(chapter);
|
||||||
Intent intent = ReaderActivity.newIntent(getActivity());
|
Intent intent = ReaderActivity.newIntent(getActivity());
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update download status of chapter
|
||||||
|
*
|
||||||
|
* @param download download object containing download progress.
|
||||||
|
*/
|
||||||
|
public void onChapterStatusChange(Download download) {
|
||||||
|
RecentChaptersHolder holder = getHolder(download.chapter);
|
||||||
|
if (holder != null)
|
||||||
|
holder.onStatusChange(download.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private RecentChaptersHolder getHolder(Chapter chapter) {
|
||||||
|
return (RecentChaptersHolder) recyclerView.findViewHolderForItemId(chapter.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start downloading chapter
|
||||||
|
*
|
||||||
|
* @param chapters selected chapters
|
||||||
|
* @param manga manga that belongs to chapter
|
||||||
|
* @return true
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("SameReturnValue")
|
||||||
|
protected boolean onDownload(Observable<Chapter> chapters, Manga manga) {
|
||||||
|
// Start the download service.
|
||||||
|
DownloadService.start(getActivity());
|
||||||
|
|
||||||
|
// Refresh data on download competition.
|
||||||
|
Observable<Chapter> observable = chapters
|
||||||
|
.doOnCompleted(adapter::notifyDataSetChanged);
|
||||||
|
|
||||||
|
// Download chapter.
|
||||||
|
getPresenter().downloadChapter(observable, manga);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start deleting chapter
|
||||||
|
* @param chapters selected chapters
|
||||||
|
* @param manga manga that belongs to chapter
|
||||||
|
* @return success of deletion.
|
||||||
|
*/
|
||||||
|
protected boolean onDelete(Observable<Chapter> chapters, Manga manga) {
|
||||||
|
int size = adapter.getSelectedItemCount();
|
||||||
|
|
||||||
|
MaterialDialog dialog = new MaterialDialog.Builder(getActivity())
|
||||||
|
.title(R.string.deleting)
|
||||||
|
.progress(false, size, true)
|
||||||
|
.cancelable(false)
|
||||||
|
.show();
|
||||||
|
|
||||||
|
Observable<Chapter> observable = chapters
|
||||||
|
.concatMap(chapter -> {
|
||||||
|
getPresenter().deleteChapter(chapter, manga);
|
||||||
|
return Observable.just(chapter);
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.doOnNext(chapter -> {
|
||||||
|
dialog.incrementProgress(1);
|
||||||
|
chapter.status = Download.NOT_DOWNLOADED;
|
||||||
|
})
|
||||||
|
.doOnCompleted(adapter::notifyDataSetChanged)
|
||||||
|
.finallyDo(dialog::dismiss);
|
||||||
|
|
||||||
|
getPresenter().deleteChapters(observable);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark chapter as read
|
||||||
|
*
|
||||||
|
* @param chapters selected chapter
|
||||||
|
* @return true
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("SameReturnValue")
|
||||||
|
protected boolean onMarkAsRead(Observable<Chapter> chapters) {
|
||||||
|
getPresenter().markChaptersRead(chapters, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark chapter as unread
|
||||||
|
*
|
||||||
|
* @param chapters selected chapter
|
||||||
|
* @return true
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("SameReturnValue")
|
||||||
|
protected boolean onMarkAsUnread(Observable<Chapter> chapters) {
|
||||||
|
getPresenter().markChaptersRead(chapters, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,102 @@
|
|||||||
package eu.kanade.tachiyomi.ui.recent;
|
package eu.kanade.tachiyomi.ui.recent;
|
||||||
|
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.view.Menu;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import butterknife.Bind;
|
import butterknife.Bind;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import eu.kanade.tachiyomi.R;
|
import eu.kanade.tachiyomi.R;
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter;
|
import eu.kanade.tachiyomi.data.database.models.MangaChapter;
|
||||||
|
import eu.kanade.tachiyomi.data.download.model.Download;
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
|
||||||
|
import rx.Observable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holder that contains chapter item
|
||||||
|
* Uses R.layout.item_recent_chapter.
|
||||||
|
* UI related actions should be called from here.
|
||||||
|
*/
|
||||||
public class RecentChaptersHolder extends FlexibleViewHolder {
|
public class RecentChaptersHolder extends FlexibleViewHolder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter for recent chapters
|
||||||
|
*/
|
||||||
|
private final RecentChaptersAdapter adapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextView containing chapter title
|
||||||
|
*/
|
||||||
@Bind(R.id.chapter_title) TextView chapterTitle;
|
@Bind(R.id.chapter_title) TextView chapterTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextView containing manga name
|
||||||
|
*/
|
||||||
@Bind(R.id.manga_title) TextView mangaTitle;
|
@Bind(R.id.manga_title) TextView mangaTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TextView containing download status
|
||||||
|
*/
|
||||||
|
@Bind(R.id.download_text) TextView downloadText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RelativeLayout containing popup menu with download options
|
||||||
|
*/
|
||||||
|
@Bind(R.id.chapter_menu) RelativeLayout chapterMenu;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color of read chapter
|
||||||
|
*/
|
||||||
private final int readColor;
|
private final int readColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color of unread chapter
|
||||||
|
*/
|
||||||
private final int unreadColor;
|
private final int unreadColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object containing chapter information
|
||||||
|
*/
|
||||||
|
private MangaChapter mangaChapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor of RecentChaptersHolder
|
||||||
|
* @param view view of ChapterHolder
|
||||||
|
* @param adapter adapter of ChapterHolder
|
||||||
|
* @param onListItemClickListener ClickListener
|
||||||
|
*/
|
||||||
public RecentChaptersHolder(View view, RecentChaptersAdapter adapter, OnListItemClickListener onListItemClickListener) {
|
public RecentChaptersHolder(View view, RecentChaptersAdapter adapter, OnListItemClickListener onListItemClickListener) {
|
||||||
super(view, adapter, onListItemClickListener);
|
super(view, adapter, onListItemClickListener);
|
||||||
|
this.adapter = adapter;
|
||||||
ButterKnife.bind(this, view);
|
ButterKnife.bind(this, view);
|
||||||
|
|
||||||
|
// Set colors.
|
||||||
readColor = ContextCompat.getColor(view.getContext(), R.color.hint_text);
|
readColor = ContextCompat.getColor(view.getContext(), R.color.hint_text);
|
||||||
unreadColor = ContextCompat.getColor(view.getContext(), R.color.primary_text);
|
unreadColor = ContextCompat.getColor(view.getContext(), R.color.primary_text);
|
||||||
|
|
||||||
|
//Set OnClickListener for download menu
|
||||||
|
chapterMenu.setOnClickListener(v -> v.post(() -> showPopupMenu(v)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set values of view
|
||||||
|
*
|
||||||
|
* @param item item containing chapter information
|
||||||
|
*/
|
||||||
public void onSetValues(MangaChapter item) {
|
public void onSetValues(MangaChapter item) {
|
||||||
|
this.mangaChapter = item;
|
||||||
|
|
||||||
|
// Set chapter title
|
||||||
chapterTitle.setText(item.chapter.name);
|
chapterTitle.setText(item.chapter.name);
|
||||||
|
|
||||||
|
// Set manga title
|
||||||
mangaTitle.setText(item.manga.title);
|
mangaTitle.setText(item.manga.title);
|
||||||
|
|
||||||
|
// Check if chapter is read and set correct color
|
||||||
if (item.chapter.read) {
|
if (item.chapter.read) {
|
||||||
chapterTitle.setTextColor(readColor);
|
chapterTitle.setTextColor(readColor);
|
||||||
mangaTitle.setTextColor(readColor);
|
mangaTitle.setTextColor(readColor);
|
||||||
@ -37,6 +104,84 @@ public class RecentChaptersHolder extends FlexibleViewHolder {
|
|||||||
chapterTitle.setTextColor(unreadColor);
|
chapterTitle.setTextColor(unreadColor);
|
||||||
mangaTitle.setTextColor(unreadColor);
|
mangaTitle.setTextColor(unreadColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set chapter status
|
||||||
|
onStatusChange(item.chapter.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates chapter status in view.
|
||||||
|
*
|
||||||
|
* @param status download status
|
||||||
|
*/
|
||||||
|
public void onStatusChange(int status) {
|
||||||
|
switch (status) {
|
||||||
|
case Download.QUEUE:
|
||||||
|
downloadText.setText(R.string.chapter_queued);
|
||||||
|
break;
|
||||||
|
case Download.DOWNLOADING:
|
||||||
|
downloadText.setText(R.string.chapter_downloading);
|
||||||
|
break;
|
||||||
|
case Download.DOWNLOADED:
|
||||||
|
downloadText.setText(R.string.chapter_downloaded);
|
||||||
|
break;
|
||||||
|
case Download.ERROR:
|
||||||
|
downloadText.setText(R.string.chapter_error);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
downloadText.setText("");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show pop up menu
|
||||||
|
* @param view view containing popup menu.
|
||||||
|
*/
|
||||||
|
private void showPopupMenu(View view) {
|
||||||
|
// Create a PopupMenu, giving it the clicked view for an anchor
|
||||||
|
PopupMenu popup = new PopupMenu(adapter.getFragment().getActivity(), view);
|
||||||
|
|
||||||
|
// Inflate our menu resource into the PopupMenu's Menu
|
||||||
|
popup.getMenuInflater().inflate(R.menu.chapter_recent, popup.getMenu());
|
||||||
|
|
||||||
|
// Hide download and show delete if the chapter is downloaded and
|
||||||
|
if (mangaChapter.chapter.isDownloaded()) {
|
||||||
|
Menu menu = popup.getMenu();
|
||||||
|
menu.findItem(R.id.action_download).setVisible(false);
|
||||||
|
menu.findItem(R.id.action_delete).setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide mark as unread when the chapter is unread
|
||||||
|
if (!mangaChapter.chapter.read /*&& mangaChapter.chapter.last_page_read == 0*/) {
|
||||||
|
popup.getMenu().findItem(R.id.action_mark_as_unread).setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide mark as read when the chapter is read
|
||||||
|
if (mangaChapter.chapter.read) {
|
||||||
|
popup.getMenu().findItem(R.id.action_mark_as_read).setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a listener so we are notified if a menu item is clicked
|
||||||
|
popup.setOnMenuItemClickListener(menuItem -> {
|
||||||
|
Observable<Chapter> chapterObservable = Observable.just(mangaChapter.chapter);
|
||||||
|
|
||||||
|
switch (menuItem.getItemId()) {
|
||||||
|
case R.id.action_download:
|
||||||
|
return adapter.getFragment().onDownload(chapterObservable, mangaChapter.manga);
|
||||||
|
case R.id.action_delete:
|
||||||
|
return adapter.getFragment().onDelete(chapterObservable, mangaChapter.manga);
|
||||||
|
case R.id.action_mark_as_read:
|
||||||
|
return adapter.getFragment().onMarkAsRead(chapterObservable);
|
||||||
|
case R.id.action_mark_as_unread:
|
||||||
|
return adapter.getFragment().onMarkAsUnread(chapterObservable);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Finally show the PopupMenu
|
||||||
|
popup.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.ui.recent;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -12,48 +14,185 @@ import java.util.TreeMap;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import de.greenrobot.event.EventBus;
|
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper;
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter;
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga;
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapter;
|
import eu.kanade.tachiyomi.data.database.models.MangaChapter;
|
||||||
|
import eu.kanade.tachiyomi.data.download.DownloadManager;
|
||||||
|
import eu.kanade.tachiyomi.data.download.model.Download;
|
||||||
import eu.kanade.tachiyomi.data.source.SourceManager;
|
import eu.kanade.tachiyomi.data.source.SourceManager;
|
||||||
import eu.kanade.tachiyomi.data.source.base.Source;
|
import eu.kanade.tachiyomi.data.source.base.Source;
|
||||||
|
import eu.kanade.tachiyomi.event.DownloadChaptersEvent;
|
||||||
import eu.kanade.tachiyomi.event.ReaderEvent;
|
import eu.kanade.tachiyomi.event.ReaderEvent;
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
|
||||||
import rx.Observable;
|
import rx.Observable;
|
||||||
import rx.android.schedulers.AndroidSchedulers;
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
|
import rx.schedulers.Schedulers;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter of RecentChaptersFragment.
|
||||||
|
* Contains information and data for fragment.
|
||||||
|
* Observable updates should be called from here.
|
||||||
|
*/
|
||||||
public class RecentChaptersPresenter extends BasePresenter<RecentChaptersFragment> {
|
public class RecentChaptersPresenter extends BasePresenter<RecentChaptersFragment> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the restartable.
|
||||||
|
*/
|
||||||
|
private static final int GET_RECENT_CHAPTERS = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the restartable.
|
||||||
|
*/
|
||||||
|
private static final int CHAPTER_STATUS_CHANGES = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to connect to database
|
||||||
|
*/
|
||||||
@Inject DatabaseHelper db;
|
@Inject DatabaseHelper db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to get information from download manager
|
||||||
|
*/
|
||||||
|
@Inject DownloadManager downloadManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to get source from source id
|
||||||
|
*/
|
||||||
@Inject SourceManager sourceManager;
|
@Inject SourceManager sourceManager;
|
||||||
|
|
||||||
private static final int GET_RECENT_CHAPTERS = 1;
|
/**
|
||||||
|
* List containing chapter and manga information
|
||||||
|
*/
|
||||||
|
private List<MangaChapter> mangaChapters;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedState) {
|
protected void onCreate(Bundle savedState) {
|
||||||
super.onCreate(savedState);
|
super.onCreate(savedState);
|
||||||
|
|
||||||
|
// Used to get recent chapters
|
||||||
restartableLatestCache(GET_RECENT_CHAPTERS,
|
restartableLatestCache(GET_RECENT_CHAPTERS,
|
||||||
this::getRecentChaptersObservable,
|
this::getRecentChaptersObservable,
|
||||||
RecentChaptersFragment::onNextMangaChapters);
|
(recentChaptersFragment, chapters) -> {
|
||||||
|
// Update adapter to show recent manga's
|
||||||
|
recentChaptersFragment.onNextMangaChapters(chapters);
|
||||||
|
// Update download status
|
||||||
|
updateChapterStatus(convertToMangaChaptersList(chapters));
|
||||||
|
});
|
||||||
|
|
||||||
if (savedState == null)
|
// Used to update download status
|
||||||
|
startableLatestCache(CHAPTER_STATUS_CHANGES,
|
||||||
|
this::getChapterStatusObs,
|
||||||
|
RecentChaptersFragment::onChapterStatusChange,
|
||||||
|
(view, error) -> Timber.e(error.getCause(), error.getMessage()));
|
||||||
|
|
||||||
|
if (savedState == null) {
|
||||||
|
// Start fetching recent chapters
|
||||||
start(GET_RECENT_CHAPTERS);
|
start(GET_RECENT_CHAPTERS);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list only containing MangaChapter objects.
|
||||||
|
*
|
||||||
|
* @param input the list that will be converted.
|
||||||
|
* @return list containing MangaChapters objects.
|
||||||
|
*/
|
||||||
|
private List<MangaChapter> convertToMangaChaptersList(List<Object> input) {
|
||||||
|
// Create temp list
|
||||||
|
List<MangaChapter> tempMangaChapterList = new ArrayList<>();
|
||||||
|
|
||||||
|
// Only add MangaChapter objects
|
||||||
|
//noinspection Convert2streamapi
|
||||||
|
for (Object object : input) {
|
||||||
|
if (object instanceof MangaChapter) {
|
||||||
|
tempMangaChapterList.add((MangaChapter) object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return temp list
|
||||||
|
return tempMangaChapterList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update status of chapters
|
||||||
|
*
|
||||||
|
* @param mangaChapters list containing recent chapters
|
||||||
|
*/
|
||||||
|
private void updateChapterStatus(List<MangaChapter> mangaChapters) {
|
||||||
|
// Set global list of chapters.
|
||||||
|
this.mangaChapters = mangaChapters;
|
||||||
|
|
||||||
|
// Update status.
|
||||||
|
//noinspection Convert2streamapi
|
||||||
|
for (MangaChapter mangaChapter : mangaChapters)
|
||||||
|
setChapterStatus(mangaChapter);
|
||||||
|
|
||||||
|
// Start onChapterStatusChange restartable.
|
||||||
|
start(CHAPTER_STATUS_CHANGES);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns observable containing chapter status.
|
||||||
|
*
|
||||||
|
* @return download object containing download progress.
|
||||||
|
*/
|
||||||
|
private Observable<Download> getChapterStatusObs() {
|
||||||
|
return downloadManager.getQueue().getStatusObservable()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.filter(download -> chapterIdEquals(download.chapter.id))
|
||||||
|
.doOnNext(this::updateChapterStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to check if chapter is in recent list
|
||||||
|
* @param chaptersId id of chapter
|
||||||
|
* @return exist in recent list
|
||||||
|
*/
|
||||||
|
private boolean chapterIdEquals(Long chaptersId) {
|
||||||
|
for (MangaChapter mangaChapter : mangaChapters) {
|
||||||
|
if (chaptersId.equals(mangaChapter.chapter.id)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update status of chapters.
|
||||||
|
*
|
||||||
|
* @param download download object containing progress.
|
||||||
|
*/
|
||||||
|
private void updateChapterStatus(Download download) {
|
||||||
|
// Loop through list
|
||||||
|
for (MangaChapter item : mangaChapters) {
|
||||||
|
if (download.chapter.id.equals(item.chapter.id)) {
|
||||||
|
item.chapter.status = download.getStatus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get observable containing recent chapters and date
|
||||||
|
* @return observable containing recent chapters and date
|
||||||
|
*/
|
||||||
private Observable<List<Object>> getRecentChaptersObservable() {
|
private Observable<List<Object>> getRecentChaptersObservable() {
|
||||||
|
// Set date for recent chapters
|
||||||
Calendar cal = Calendar.getInstance();
|
Calendar cal = Calendar.getInstance();
|
||||||
cal.setTime(new Date());
|
cal.setTime(new Date());
|
||||||
cal.add(Calendar.MONTH, -1);
|
cal.add(Calendar.MONTH, -1);
|
||||||
|
|
||||||
|
// Get recent chapters from database.
|
||||||
return db.getRecentChapters(cal.getTime()).asRxObservable()
|
return db.getRecentChapters(cal.getTime()).asRxObservable()
|
||||||
// group chapters by the date they were fetched on a ordered map
|
// Group chapters by the date they were fetched on a ordered map.
|
||||||
.flatMap(recents -> Observable.from(recents)
|
.flatMap(recents -> Observable.from(recents)
|
||||||
.toMultimap(
|
.toMultimap(
|
||||||
recent -> getMapKey(recent.chapter.date_fetch),
|
recent -> getMapKey(recent.chapter.date_fetch),
|
||||||
recent -> recent,
|
recent -> recent,
|
||||||
() -> new TreeMap<>((d1, d2) -> d2.compareTo(d1))))
|
() -> new TreeMap<>((d1, d2) -> d2.compareTo(d1))))
|
||||||
// add every day and all its chapters to a single list
|
// Add every day and all its chapters to a single list.
|
||||||
.map(recents -> {
|
.map(recents -> {
|
||||||
List<Object> items = new ArrayList<>();
|
List<Object> items = new ArrayList<>();
|
||||||
for (Map.Entry<Date, Collection<MangaChapter>> recent : recents.entrySet()) {
|
for (Map.Entry<Date, Collection<MangaChapter>> recent : recents.entrySet()) {
|
||||||
@ -65,6 +204,35 @@ public class RecentChaptersPresenter extends BasePresenter<RecentChaptersFragmen
|
|||||||
.observeOn(AndroidSchedulers.mainThread());
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the chapter status
|
||||||
|
* @param mangaChapter MangaChapter which status gets updated
|
||||||
|
*/
|
||||||
|
private void setChapterStatus(MangaChapter mangaChapter) {
|
||||||
|
// Check if chapter in queue
|
||||||
|
for (Download download : downloadManager.getQueue()) {
|
||||||
|
if (mangaChapter.chapter.id.equals(download.chapter.id)) {
|
||||||
|
mangaChapter.chapter.status = download.getStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get source of chapter
|
||||||
|
Source source = sourceManager.get(mangaChapter.manga.source);
|
||||||
|
|
||||||
|
// Check if chapter is downloaded
|
||||||
|
if (downloadManager.isChapterDownloaded(source, mangaChapter.manga, mangaChapter.chapter)) {
|
||||||
|
mangaChapter.chapter.status = Download.DOWNLOADED;
|
||||||
|
} else {
|
||||||
|
mangaChapter.chapter.status = Download.NOT_DOWNLOADED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get date as time key
|
||||||
|
* @param date desired date
|
||||||
|
* @return date as time key
|
||||||
|
*/
|
||||||
private Date getMapKey(long date) {
|
private Date getMapKey(long date) {
|
||||||
Calendar cal = Calendar.getInstance();
|
Calendar cal = Calendar.getInstance();
|
||||||
cal.setTime(new Date(date));
|
cal.setTime(new Date(date));
|
||||||
@ -75,8 +243,67 @@ public class RecentChaptersPresenter extends BasePresenter<RecentChaptersFragmen
|
|||||||
return cal.getTime();
|
return cal.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open chapter in reader
|
||||||
|
* @param item chapter that is opened
|
||||||
|
*/
|
||||||
public void onOpenChapter(MangaChapter item) {
|
public void onOpenChapter(MangaChapter item) {
|
||||||
Source source = sourceManager.get(item.manga.source);
|
Source source = sourceManager.get(item.manga.source);
|
||||||
EventBus.getDefault().postSticky(new ReaderEvent(source, item.manga, item.chapter));
|
EventBus.getDefault().postSticky(new ReaderEvent(source, item.manga, item.chapter));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download selected chapter
|
||||||
|
* @param selectedChapter chapter that is selected
|
||||||
|
* @param manga manga that belongs to chapter
|
||||||
|
*/
|
||||||
|
public void downloadChapter(Observable<Chapter> selectedChapter, Manga manga) {
|
||||||
|
add(selectedChapter
|
||||||
|
.toList()
|
||||||
|
.subscribe(chapters -> {
|
||||||
|
EventBus.getDefault().postSticky(new DownloadChaptersEvent(manga, chapters));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete selected chapter
|
||||||
|
* @param chapter chapter that is selected
|
||||||
|
* @param manga manga that belongs to chapter
|
||||||
|
*/
|
||||||
|
public void deleteChapter(Chapter chapter, Manga manga) {
|
||||||
|
Source source = sourceManager.get(manga.source);
|
||||||
|
downloadManager.deleteChapter(source, manga, chapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete selected chapter observable
|
||||||
|
* @param selectedChapters chapter that are selected
|
||||||
|
*/
|
||||||
|
public void deleteChapters(Observable<Chapter> selectedChapters) {
|
||||||
|
add(selectedChapters
|
||||||
|
.subscribe(chapter -> {
|
||||||
|
downloadManager.getQueue().remove(chapter);
|
||||||
|
}, error -> {
|
||||||
|
Timber.e(error.getMessage());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark selected chapter as read
|
||||||
|
* @param selectedChapters chapter that is selected
|
||||||
|
* @param read read status
|
||||||
|
*/
|
||||||
|
public void markChaptersRead(Observable<Chapter> selectedChapters, boolean read) {
|
||||||
|
add(selectedChapters
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.map(chapter -> {
|
||||||
|
chapter.read = read;
|
||||||
|
if (!read) chapter.last_page_read = 0;
|
||||||
|
return chapter;
|
||||||
|
})
|
||||||
|
.toList()
|
||||||
|
.flatMap(chapters -> db.insertChapters(chapters).asRxObservable())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@ -15,8 +17,23 @@ import java.util.TimeZone;
|
|||||||
|
|
||||||
import eu.kanade.tachiyomi.BuildConfig;
|
import eu.kanade.tachiyomi.BuildConfig;
|
||||||
import eu.kanade.tachiyomi.R;
|
import eu.kanade.tachiyomi.R;
|
||||||
|
import eu.kanade.tachiyomi.data.updater.UpdateChecker;
|
||||||
|
import eu.kanade.tachiyomi.data.updater.UpdateDownloader;
|
||||||
|
import eu.kanade.tachiyomi.util.ToastUtil;
|
||||||
|
import rx.Subscription;
|
||||||
|
import rx.android.schedulers.AndroidSchedulers;
|
||||||
|
import rx.schedulers.Schedulers;
|
||||||
|
|
||||||
public class SettingsAboutFragment extends SettingsNestedFragment {
|
public class SettingsAboutFragment extends SettingsNestedFragment {
|
||||||
|
/**
|
||||||
|
* Checks for new releases
|
||||||
|
*/
|
||||||
|
private UpdateChecker updateChecker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The subscribtion service of the obtained release object
|
||||||
|
*/
|
||||||
|
private Subscription releaseSubscription;
|
||||||
|
|
||||||
public static SettingsNestedFragment newInstance(int resourcePreference, int resourceTitle) {
|
public static SettingsNestedFragment newInstance(int resourcePreference, int resourceTitle) {
|
||||||
SettingsNestedFragment fragment = new SettingsAboutFragment();
|
SettingsNestedFragment fragment = new SettingsAboutFragment();
|
||||||
@ -25,14 +42,36 @@ public class SettingsAboutFragment extends SettingsNestedFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
//Check for update
|
||||||
|
updateChecker = new UpdateChecker(getActivity());
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
if (releaseSubscription != null)
|
||||||
|
releaseSubscription.unsubscribe();
|
||||||
|
|
||||||
|
super.onDestroyView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
|
||||||
Preference version = findPreference(getString(R.string.pref_version));
|
Preference version = findPreference(getString(R.string.pref_version));
|
||||||
Preference buildTime = findPreference(getString(R.string.pref_build_time));
|
Preference buildTime = findPreference(getString(R.string.pref_build_time));
|
||||||
|
|
||||||
version.setSummary(BuildConfig.DEBUG ? "r" + BuildConfig.COMMIT_COUNT :
|
version.setSummary(BuildConfig.DEBUG ? "r" + BuildConfig.COMMIT_COUNT :
|
||||||
BuildConfig.VERSION_NAME);
|
BuildConfig.VERSION_NAME);
|
||||||
|
|
||||||
|
//Set onClickListener to check for new version
|
||||||
|
version.setOnPreferenceClickListener(preference -> {
|
||||||
|
if (!BuildConfig.DEBUG && BuildConfig.INCLUDE_UPDATER)
|
||||||
|
checkVersion();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
buildTime.setSummary(getFormattedBuildTime());
|
buildTime.setSummary(getFormattedBuildTime());
|
||||||
|
|
||||||
return super.onCreateView(inflater, container, savedState);
|
return super.onCreateView(inflater, container, savedState);
|
||||||
@ -54,4 +93,40 @@ public class SettingsAboutFragment extends SettingsNestedFragment {
|
|||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks version and shows a user prompt when update available.
|
||||||
|
*/
|
||||||
|
private void checkVersion() {
|
||||||
|
releaseSubscription = updateChecker.checkForApplicationUpdate()
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(release -> {
|
||||||
|
//Get version of latest release
|
||||||
|
String newVersion = release.getVersion();
|
||||||
|
newVersion = newVersion.replaceAll("[^\\d.]", "");
|
||||||
|
|
||||||
|
//Check if latest version is different from current version
|
||||||
|
if (!newVersion.equals(BuildConfig.VERSION_NAME)) {
|
||||||
|
String downloadLink = release.getDownloadLink();
|
||||||
|
String body = release.getChangeLog();
|
||||||
|
|
||||||
|
//Create confirmation window
|
||||||
|
new MaterialDialog.Builder(getActivity())
|
||||||
|
.title(getString(R.string.update_check_title))
|
||||||
|
.content(body)
|
||||||
|
.positiveText(getString(R.string.update_check_confirm))
|
||||||
|
.negativeText(getString(R.string.update_check_ignore))
|
||||||
|
.onPositive((dialog, which) -> {
|
||||||
|
// User output that download has started
|
||||||
|
ToastUtil.showShort(getActivity(), getString(R.string.update_check_download_started));
|
||||||
|
// Start download
|
||||||
|
new UpdateDownloader(getActivity().getApplicationContext()).execute(downloadLink);
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
ToastUtil.showShort(getActivity(), getString(R.string.update_check_no_new_updates));
|
||||||
|
}
|
||||||
|
}, Throwable::printStackTrace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.setting;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
@ -61,6 +62,13 @@ public class SettingsDownloadsFragment extends SettingsNestedFragment {
|
|||||||
if (requestCode == DOWNLOAD_DIR_CODE && resultCode == Activity.RESULT_OK) {
|
if (requestCode == DOWNLOAD_DIR_CODE && resultCode == Activity.RESULT_OK) {
|
||||||
Uri uri = data.getData();
|
Uri uri = data.getData();
|
||||||
preferences.setDownloadsDirectory(uri.getPath());
|
preferences.setDownloadsDirectory(uri.getPath());
|
||||||
|
|
||||||
|
// Persist access permissions.
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
getActivity().getContentResolver().takePersistableUriPermission(uri,
|
||||||
|
Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||||
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,12 +10,18 @@ import eu.kanade.tachiyomi.data.database.models.Manga;
|
|||||||
|
|
||||||
public class ChapterRecognition {
|
public class ChapterRecognition {
|
||||||
|
|
||||||
private static final Pattern p1 = Pattern.compile("ch[^0-9]?\\s*(\\d+[\\.,]?\\d*)");
|
private static final Pattern cleanWithToken = Pattern.compile("ch[^0-9]?\\s*(\\d+[\\.,]?\\d+)($|\\b)");
|
||||||
private static final Pattern p2 = Pattern.compile("(\\d+[\\.,]?\\d*)");
|
private static final Pattern uncleanWithToken = Pattern.compile("ch[^0-9]?\\s*(\\d+[\\.,]?\\d*)");
|
||||||
private static final Pattern p3 = Pattern.compile("(\\d+[\\.,]?\\d*\\s*:)");
|
private static final Pattern withAlphaPostfix = Pattern.compile("(\\d+[\\.,]?\\d*\\s*)([a-z])($|\\b)");
|
||||||
|
private static final Pattern cleanNumber = Pattern.compile("(\\d+[\\.,]?\\d+)($|\\b)");
|
||||||
|
private static final Pattern uncleanNumber = Pattern.compile("(\\d+[\\.,]?\\d*)");
|
||||||
|
private static final Pattern withColon = Pattern.compile("(\\d+[\\.,]?\\d*\\s*:)([^\\d]|$)");
|
||||||
|
private static final Pattern startingNumber = Pattern.compile("^(\\d+[\\.,]?\\d*)");
|
||||||
|
|
||||||
private static final Pattern pUnwanted =
|
private static final Pattern pUnwanted =
|
||||||
Pattern.compile("\\b(v|ver|vol|version|volume)\\.?\\s*\\d+\\b");
|
Pattern.compile("(\\b|\\d)(v|ver|vol|version|volume)\\.?\\s*\\d+\\b");
|
||||||
|
private static final Pattern pPart =
|
||||||
|
Pattern.compile("(\\b|\\d)part\\s*\\d+.+");
|
||||||
|
|
||||||
public static void parseChapterNumber(Chapter chapter, Manga manga) {
|
public static void parseChapterNumber(Chapter chapter, Manga manga) {
|
||||||
if (chapter.chapter_number != -1)
|
if (chapter.chapter_number != -1)
|
||||||
@ -24,20 +30,34 @@ public class ChapterRecognition {
|
|||||||
String name = chapter.name.toLowerCase();
|
String name = chapter.name.toLowerCase();
|
||||||
Matcher matcher;
|
Matcher matcher;
|
||||||
|
|
||||||
// Safest option, the chapter has a token prepended
|
// Safest option, the chapter has a token prepended and nothing at the end of the number
|
||||||
matcher = p1.matcher(name);
|
matcher = cleanWithToken.matcher(name);
|
||||||
|
if (matcher.find()) {
|
||||||
|
chapter.chapter_number = Float.parseFloat(matcher.group(1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a number with a single alpha prefix is parsed as sub-chapter
|
||||||
|
matcher = withAlphaPostfix.matcher(name);
|
||||||
|
if (matcher.find()) {
|
||||||
|
chapter.chapter_number = Float.parseFloat(matcher.group(1)) + parseAlphaPostFix(matcher.group(2));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the chapter has a token prepended and something at the end of the number
|
||||||
|
matcher = uncleanWithToken.matcher(name);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
chapter.chapter_number = Float.parseFloat(matcher.group(1));
|
chapter.chapter_number = Float.parseFloat(matcher.group(1));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove anything related to the volume or version
|
// Remove anything related to the volume or version
|
||||||
name = pUnwanted.matcher(name).replaceAll("");
|
name = pUnwanted.matcher(name).replaceAll("$1");
|
||||||
|
|
||||||
List<Float> occurrences;
|
List<Float> occurrences;
|
||||||
|
|
||||||
// If there's only one number, use it
|
// If there's only one number, use it
|
||||||
matcher = p2.matcher(name);
|
matcher = uncleanNumber.matcher(name);
|
||||||
occurrences = getAllOccurrences(matcher);
|
occurrences = getAllOccurrences(matcher);
|
||||||
if (occurrences.size() == 1) {
|
if (occurrences.size() == 1) {
|
||||||
chapter.chapter_number = occurrences.get(0);
|
chapter.chapter_number = occurrences.get(0);
|
||||||
@ -45,7 +65,15 @@ public class ChapterRecognition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If it has a colon, the chapter number should be that one
|
// If it has a colon, the chapter number should be that one
|
||||||
matcher = p3.matcher(name);
|
matcher = withColon.matcher(name);
|
||||||
|
occurrences = getAllOccurrences(matcher);
|
||||||
|
if (occurrences.size() == 1) {
|
||||||
|
chapter.chapter_number = occurrences.get(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer numbers without anything appended
|
||||||
|
matcher = cleanNumber.matcher(name);
|
||||||
occurrences = getAllOccurrences(matcher);
|
occurrences = getAllOccurrences(matcher);
|
||||||
if (occurrences.size() == 1) {
|
if (occurrences.size() == 1) {
|
||||||
chapter.chapter_number = occurrences.get(0);
|
chapter.chapter_number = occurrences.get(0);
|
||||||
@ -57,9 +85,9 @@ public class ChapterRecognition {
|
|||||||
|
|
||||||
// Try to remove the manga name from the chapter, and try again
|
// Try to remove the manga name from the chapter, and try again
|
||||||
String mangaName = replaceIrrelevantCharacters(manga.title);
|
String mangaName = replaceIrrelevantCharacters(manga.title);
|
||||||
String nameWithoutManga = difference(mangaName, name);
|
String nameWithoutManga = difference(mangaName, name).trim();
|
||||||
if (!nameWithoutManga.isEmpty()) {
|
if (!nameWithoutManga.isEmpty()) {
|
||||||
matcher = p2.matcher(nameWithoutManga);
|
matcher = uncleanNumber.matcher(nameWithoutManga);
|
||||||
occurrences = getAllOccurrences(matcher);
|
occurrences = getAllOccurrences(matcher);
|
||||||
if (occurrences.size() == 1) {
|
if (occurrences.size() == 1) {
|
||||||
chapter.chapter_number = occurrences.get(0);
|
chapter.chapter_number = occurrences.get(0);
|
||||||
@ -69,6 +97,52 @@ public class ChapterRecognition {
|
|||||||
|
|
||||||
// TODO more checks (maybe levenshtein?)
|
// TODO more checks (maybe levenshtein?)
|
||||||
|
|
||||||
|
// try splitting the name in parts an pick the first valid one
|
||||||
|
String[] nameParts = chapter.name.split("-");
|
||||||
|
Chapter dummyChapter = Chapter.create();
|
||||||
|
if (nameParts.length > 1) {
|
||||||
|
for (String part : nameParts) {
|
||||||
|
dummyChapter.name = part;
|
||||||
|
parseChapterNumber(dummyChapter, manga);
|
||||||
|
if (dummyChapter.chapter_number >= 0) {
|
||||||
|
chapter.chapter_number = dummyChapter.chapter_number;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip anything after "part xxx" and try that
|
||||||
|
matcher = pPart.matcher(name);
|
||||||
|
if (matcher.find()) {
|
||||||
|
name = pPart.matcher(name).replaceAll("$1");
|
||||||
|
dummyChapter.name = name;
|
||||||
|
parseChapterNumber(dummyChapter, manga);
|
||||||
|
if (dummyChapter.chapter_number >= 0) {
|
||||||
|
chapter.chapter_number = dummyChapter.chapter_number;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// check for a number either at the start or right after the manga title
|
||||||
|
matcher = startingNumber.matcher(name);
|
||||||
|
if (matcher.find()) {
|
||||||
|
chapter.chapter_number = Float.parseFloat(matcher.group(1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
matcher = startingNumber.matcher(nameWithoutManga);
|
||||||
|
if (matcher.find()) {
|
||||||
|
chapter.chapter_number = Float.parseFloat(matcher.group(1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* x.a -> x.1, x.b -> x.2, etc
|
||||||
|
*/
|
||||||
|
private static float parseAlphaPostFix(String postfix) {
|
||||||
|
char alpha = postfix.charAt(0);
|
||||||
|
return Float.parseFloat("0." + Integer.toString((int)alpha - 96));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Float> getAllOccurrences(Matcher matcher) {
|
public static List<Float> getAllOccurrences(Matcher matcher) {
|
||||||
@ -76,7 +150,7 @@ public class ChapterRecognition {
|
|||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
// Match again to get only numbers from the captured text
|
// Match again to get only numbers from the captured text
|
||||||
String text = matcher.group();
|
String text = matcher.group();
|
||||||
Matcher m = p2.matcher(text);
|
Matcher m = uncleanNumber.matcher(text);
|
||||||
if (m.find()) {
|
if (m.find()) {
|
||||||
try {
|
try {
|
||||||
Float value = Float.parseFloat(m.group(1));
|
Float value = Float.parseFloat(m.group(1));
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.util;
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
@Target({ElementType.METHOD})
|
|
||||||
public @interface EventBusHook {}
|
|
@ -5,6 +5,9 @@ import android.content.res.TypedArray;
|
|||||||
import android.support.v7.widget.GridLayoutManager;
|
import android.support.v7.widget.GridLayoutManager;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.R;
|
||||||
|
|
||||||
public class AutofitRecyclerView extends RecyclerView {
|
public class AutofitRecyclerView extends RecyclerView {
|
||||||
|
|
||||||
|
6
app/src/main/res/anim/fab_hide_to_bottom.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:interpolator="@android:interpolator/accelerate_quint"
|
||||||
|
android:fromYDelta="0"
|
||||||
|
android:toYDelta="30%p"
|
||||||
|
android:duration="200" />
|
6
app/src/main/res/anim/fab_show_from_bottom.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<translate xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:interpolator="@android:interpolator/accelerate_cubic"
|
||||||
|
android:fromYDelta="30%p"
|
||||||
|
android:toYDelta="0"
|
||||||
|
android:duration="300" />
|
Before Width: | Height: | Size: 134 B |
Before Width: | Height: | Size: 387 B |
Before Width: | Height: | Size: 651 B |
Before Width: | Height: | Size: 584 B |
BIN
app/src/main/res/drawable-hdpi/ic_add_white_24dp.png
Normal file
After Width: | Height: | Size: 127 B |
BIN
app/src/main/res/drawable-hdpi/ic_bookmark_border_white_24dp.png
Normal file
After Width: | Height: | Size: 239 B |
BIN
app/src/main/res/drawable-hdpi/ic_bookmark_white_24dp.png
Normal file
After Width: | Height: | Size: 185 B |
BIN
app/src/main/res/drawable-hdpi/ic_crop_original_white_24dp.png
Normal file
After Width: | Height: | Size: 271 B |
BIN
app/src/main/res/drawable-hdpi/ic_favorite_border_white_24dp.png
Normal file
After Width: | Height: | Size: 532 B |
BIN
app/src/main/res/drawable-hdpi/ic_favorite_white_24dp.png
Normal file
After Width: | Height: | Size: 358 B |
BIN
app/src/main/res/drawable-hdpi/ic_mode_edit_white_24dp.png
Normal file
After Width: | Height: | Size: 219 B |
BIN
app/src/main/res/drawable-hdpi/ic_pause.png
Normal file
After Width: | Height: | Size: 105 B |
Before Width: | Height: | Size: 119 B |
BIN
app/src/main/res/drawable-ldpi/ic_crop_original_white_24dp.png
Normal file
After Width: | Height: | Size: 237 B |
BIN
app/src/main/res/drawable-ldpi/ic_pause.png
Normal file
After Width: | Height: | Size: 95 B |
Before Width: | Height: | Size: 116 B |
Before Width: | Height: | Size: 262 B |
Before Width: | Height: | Size: 449 B |