From ee7d76e775d78584b9f9ffea241750fe70eaed00 Mon Sep 17 00:00:00 2001 From: inorichi Date: Wed, 25 Nov 2015 16:08:24 +0100 Subject: [PATCH 1/4] Initial MAL support --- app/build.gradle | 6 +- .../data/chaptersync/BaseChapterSync.java | 16 ++ .../data/chaptersync/ChapterSyncManager.java | 29 ++++ .../data/chaptersync/MyAnimeList.java | 163 ++++++++++++++++++ .../data/database/DatabaseHelper.java | 38 ++++ .../mangafeed/data/database/DbOpenHelper.java | 6 +- .../data/database/models/ChapterSync.java | 35 ++++ .../database/tables/ChapterSyncTable.java | 35 ++++ .../data/preference/PreferencesHelper.java | 18 ++ .../injection/component/AppComponent.java | 7 + .../injection/module/DataModule.java | 7 + .../mangafeed/ui/manga/MangaActivity.java | 31 +++- .../ui/manga/info/MangaInfoFragment.java | 26 +-- .../MyAnimeListDialogFragment.java | 99 +++++++++++ .../myanimelist/MyAnimeListFragment.java | 85 +++++++++ .../myanimelist/MyAnimeListPresenter.java | 105 +++++++++++ .../ui/setting/LoginDialogPreference.java | 124 ------------- .../ui/setting/SettingsAccountsFragment.java | 29 +++- .../dialog/ChapterSyncLoginDialog.java | 74 ++++++++ .../setting/dialog/LoginDialogPreference.java | 78 +++++++++ .../ui/setting/dialog/SourceLoginDialog.java | 74 ++++++++ app/src/main/res/drawable-hdpi/ic_create.png | Bin 0 -> 262 bytes app/src/main/res/drawable-ldpi/ic_create.png | Bin 0 -> 195 bytes app/src/main/res/drawable-mdpi/ic_create.png | Bin 0 -> 195 bytes app/src/main/res/drawable-xhdpi/ic_create.png | Bin 0 -> 299 bytes .../main/res/drawable-xxhdpi/ic_create.png | Bin 0 -> 415 bytes .../main/res/drawable-xxxhdpi/ic_create.png | Bin 0 -> 501 bytes .../res/layout/dialog_myanimelist_search.xml | 36 ++++ .../layout/dialog_myanimelist_search_item.xml | 13 ++ .../main/res/layout/fragment_myanimelist.xml | 49 ++++++ app/src/main/res/menu/myanimelist.xml | 10 ++ app/src/main/res/values/strings.xml | 7 + build.gradle | 5 - 33 files changed, 1037 insertions(+), 168 deletions(-) create mode 100644 app/src/main/java/eu/kanade/mangafeed/data/chaptersync/BaseChapterSync.java create mode 100644 app/src/main/java/eu/kanade/mangafeed/data/chaptersync/ChapterSyncManager.java create mode 100644 app/src/main/java/eu/kanade/mangafeed/data/chaptersync/MyAnimeList.java create mode 100644 app/src/main/java/eu/kanade/mangafeed/data/database/models/ChapterSync.java create mode 100644 app/src/main/java/eu/kanade/mangafeed/data/database/tables/ChapterSyncTable.java create mode 100644 app/src/main/java/eu/kanade/mangafeed/ui/manga/myanimelist/MyAnimeListDialogFragment.java create mode 100644 app/src/main/java/eu/kanade/mangafeed/ui/manga/myanimelist/MyAnimeListFragment.java create mode 100644 app/src/main/java/eu/kanade/mangafeed/ui/manga/myanimelist/MyAnimeListPresenter.java delete mode 100644 app/src/main/java/eu/kanade/mangafeed/ui/setting/LoginDialogPreference.java create mode 100644 app/src/main/java/eu/kanade/mangafeed/ui/setting/dialog/ChapterSyncLoginDialog.java create mode 100644 app/src/main/java/eu/kanade/mangafeed/ui/setting/dialog/LoginDialogPreference.java create mode 100644 app/src/main/java/eu/kanade/mangafeed/ui/setting/dialog/SourceLoginDialog.java create mode 100644 app/src/main/res/drawable-hdpi/ic_create.png create mode 100644 app/src/main/res/drawable-ldpi/ic_create.png create mode 100644 app/src/main/res/drawable-mdpi/ic_create.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_create.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_create.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_create.png create mode 100644 app/src/main/res/layout/dialog_myanimelist_search.xml create mode 100644 app/src/main/res/layout/dialog_myanimelist_search_item.xml create mode 100644 app/src/main/res/layout/fragment_myanimelist.xml create mode 100644 app/src/main/res/menu/myanimelist.xml diff --git a/app/build.gradle b/app/build.gradle index 044ee56b5..88e27b97a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,4 @@ apply plugin: 'com.android.application' -// This does not break the build when Android Studio is missing the JRebel for Android plugin. -apply plugin: 'com.zeroturnaround.jrebel.android' apply plugin: 'com.neenbedankt.android-apt' apply plugin: 'me.tatarka.retrolambda' @@ -65,8 +63,8 @@ dependencies { compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION" compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION" compile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION" - compile 'com.squareup.okhttp:okhttp-urlconnection:2.5.0' - compile 'com.squareup.okhttp:okhttp:2.5.0' + compile 'com.squareup.okhttp:okhttp-urlconnection:2.6.0' + compile 'com.squareup.okhttp:okhttp:2.6.0' compile 'com.squareup.okio:okio:1.6.0' compile 'com.google.code.gson:gson:2.4' compile 'com.jakewharton:disklrucache:2.0.2' diff --git a/app/src/main/java/eu/kanade/mangafeed/data/chaptersync/BaseChapterSync.java b/app/src/main/java/eu/kanade/mangafeed/data/chaptersync/BaseChapterSync.java new file mode 100644 index 000000000..90f7adc20 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/data/chaptersync/BaseChapterSync.java @@ -0,0 +1,16 @@ +package eu.kanade.mangafeed.data.chaptersync; + +import rx.Observable; + +public abstract class BaseChapterSync { + + // Name of the chapter sync service to display + public abstract String getName(); + + // Id of the sync service (must be declared and obtained from ChapterSyncManager to avoid conflicts) + public abstract int getId(); + + public abstract Observable login(String username, String password); + + public abstract boolean isLogged(); +} diff --git a/app/src/main/java/eu/kanade/mangafeed/data/chaptersync/ChapterSyncManager.java b/app/src/main/java/eu/kanade/mangafeed/data/chaptersync/ChapterSyncManager.java new file mode 100644 index 000000000..73d219bf0 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/data/chaptersync/ChapterSyncManager.java @@ -0,0 +1,29 @@ +package eu.kanade.mangafeed.data.chaptersync; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.List; + +public class ChapterSyncManager { + + private List services; + private MyAnimeList myAnimeList; + + public static final int MYANIMELIST = 1; + + public ChapterSyncManager(Context context) { + services = new ArrayList<>(); + myAnimeList = new MyAnimeList(context); + services.add(myAnimeList); + } + + public MyAnimeList getMyAnimeList() { + return myAnimeList; + } + + public List getChapterSyncServices() { + return services; + } + +} diff --git a/app/src/main/java/eu/kanade/mangafeed/data/chaptersync/MyAnimeList.java b/app/src/main/java/eu/kanade/mangafeed/data/chaptersync/MyAnimeList.java new file mode 100644 index 000000000..4e26b31c1 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/data/chaptersync/MyAnimeList.java @@ -0,0 +1,163 @@ +package eu.kanade.mangafeed.data.chaptersync; + +import android.content.Context; +import android.net.Uri; +import android.util.Xml; + +import com.squareup.okhttp.Credentials; +import com.squareup.okhttp.FormEncodingBuilder; +import com.squareup.okhttp.Headers; +import com.squareup.okhttp.Response; + +import org.jsoup.Jsoup; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; + +import javax.inject.Inject; + +import eu.kanade.mangafeed.App; +import eu.kanade.mangafeed.data.database.models.ChapterSync; +import eu.kanade.mangafeed.data.network.NetworkHelper; +import eu.kanade.mangafeed.data.preference.PreferencesHelper; +import rx.Observable; + +public class MyAnimeList extends BaseChapterSync { + + @Inject PreferencesHelper preferences; + @Inject NetworkHelper networkService; + + private Headers headers; + + public static final String BASE_URL = "http://myanimelist.net"; + + private static final String ENTRY = "entry"; + private static final String CHAPTER = "chapter"; + + public MyAnimeList(Context context) { + App.get(context).getComponent().inject(this); + + String username = preferences.getChapterSyncUsername(this); + String password = preferences.getChapterSyncPassword(this); + + if (!username.isEmpty() && !password.isEmpty()) { + createHeaders(username, password); + } + } + + @Override + public String getName() { + return "MyAnimeList"; + } + + @Override + public int getId() { + return ChapterSyncManager.MYANIMELIST; + } + + public String getLoginUrl() { + return Uri.parse(BASE_URL).buildUpon() + .appendEncodedPath("api/account/verify_credentials.xml") + .toString(); + } + + public Observable login(String username, String password) { + createHeaders(username, password); + return networkService.getResponse(getLoginUrl(), headers, null) + .map(response -> response.code() == 200); + } + + @Override + public boolean isLogged() { + return !preferences.getChapterSyncUsername(this).isEmpty() + && !preferences.getChapterSyncPassword(this).isEmpty(); + } + + public String getSearchUrl(String query) { + return Uri.parse(BASE_URL).buildUpon() + .appendEncodedPath("api/manga/search.xml") + .appendQueryParameter("q", query) + .toString(); + } + + public Observable> search(String query) { + return networkService.getStringResponse(getSearchUrl(query), headers, null) + .map(Jsoup::parse) + .flatMap(doc -> Observable.from(doc.select("entry"))) + .map(entry -> { + ChapterSync chapter = ChapterSync.create(this); + chapter.title = entry.select("title").first().text(); + chapter.remote_id = Long.parseLong(entry.select("id").first().text()); + return chapter; + }) + .toList(); + } + + public String getListUrl(String username) { + return Uri.parse(BASE_URL).buildUpon() + .appendPath("malappinfo.php") + .appendQueryParameter("u", username) + .appendQueryParameter("status", "all") + .appendQueryParameter("type", "manga") + .toString(); + } + + public Observable> getList(String username) { + return networkService.getStringResponse(getListUrl(username), headers, null) + .map(Jsoup::parse) + .flatMap(doc -> Observable.from(doc.select("manga"))) + .map(entry -> { + ChapterSync chapter = ChapterSync.create(this); + chapter.title = entry.select("series_title").first().text(); + chapter.remote_id = Long.parseLong( + entry.select("series_mangadb_id").first().text()); + chapter.last_chapter_read = Integer.parseInt( + entry.select("my_read_chapters").first().text()); + return chapter; + }) + .toList(); + } + + public String getUpdateUrl(ChapterSync chapter) { + return Uri.parse(BASE_URL).buildUpon() + .appendEncodedPath("api/mangalist/update") + .appendPath(chapter.remote_id + ".xml") + .toString(); + } + + public Observable update(ChapterSync chapter) { + XmlSerializer xml = Xml.newSerializer(); + StringWriter writer = new StringWriter(); + try { + xml.setOutput(writer); + xml.startDocument("UTF-8", false); + xml.startTag("", ENTRY); + xml.startTag("", CHAPTER); + xml.text(chapter.last_chapter_read + ""); + xml.endTag("", CHAPTER); + xml.endTag("", ENTRY); + xml.endDocument(); + } catch (IOException e) { + return Observable.error(e); + } + + FormEncodingBuilder form = new FormEncodingBuilder(); + form.add("data", writer.toString()); + + return networkService.postData(getUpdateUrl(chapter), form.build(), headers); + } + + public void createHeaders(String username, String password) { + Headers.Builder builder = new Headers.Builder(); + builder.add("Authorization", Credentials.basic(username, password)); +// builder.add("User-Agent", ""); + setHeaders(builder.build()); + } + + public void setHeaders(Headers headers) { + this.headers = headers; + } + +} diff --git a/app/src/main/java/eu/kanade/mangafeed/data/database/DatabaseHelper.java b/app/src/main/java/eu/kanade/mangafeed/data/database/DatabaseHelper.java index 0b475f9e2..54d4ff1cd 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/database/DatabaseHelper.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/database/DatabaseHelper.java @@ -16,14 +16,20 @@ import com.pushtorefresh.storio.sqlite.queries.RawQuery; import java.util.List; +import eu.kanade.mangafeed.data.chaptersync.BaseChapterSync; import eu.kanade.mangafeed.data.database.models.Chapter; import eu.kanade.mangafeed.data.database.models.ChapterStorIOSQLiteDeleteResolver; import eu.kanade.mangafeed.data.database.models.ChapterStorIOSQLiteGetResolver; import eu.kanade.mangafeed.data.database.models.ChapterStorIOSQLitePutResolver; +import eu.kanade.mangafeed.data.database.models.ChapterSync; +import eu.kanade.mangafeed.data.database.models.ChapterSyncStorIOSQLiteDeleteResolver; +import eu.kanade.mangafeed.data.database.models.ChapterSyncStorIOSQLiteGetResolver; +import eu.kanade.mangafeed.data.database.models.ChapterSyncStorIOSQLitePutResolver; import eu.kanade.mangafeed.data.database.models.Manga; import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLiteDeleteResolver; import eu.kanade.mangafeed.data.database.models.MangaStorIOSQLitePutResolver; import eu.kanade.mangafeed.data.database.resolvers.MangaWithUnreadGetResolver; +import eu.kanade.mangafeed.data.database.tables.ChapterSyncTable; import eu.kanade.mangafeed.data.database.tables.ChapterTable; import eu.kanade.mangafeed.data.database.tables.MangaTable; import eu.kanade.mangafeed.util.ChapterRecognition; @@ -48,6 +54,11 @@ public class DatabaseHelper { .getResolver(new ChapterStorIOSQLiteGetResolver()) .deleteResolver(new ChapterStorIOSQLiteDeleteResolver()) .build()) + .addTypeMapping(ChapterSync.class, SQLiteTypeMapping.builder() + .putResolver(new ChapterSyncStorIOSQLitePutResolver()) + .getResolver(new ChapterSyncStorIOSQLiteGetResolver()) + .deleteResolver(new ChapterSyncStorIOSQLiteDeleteResolver()) + .build()) .build(); } @@ -263,4 +274,31 @@ public class DatabaseHelper { .objects(chapters) .prepare(); } + + // Chapter sync related queries + + public PreparedGetListOfObjects getChapterSync(Manga manga, BaseChapterSync sync) { + + return db.get() + .listOfObjects(ChapterSync.class) + .withQuery(Query.builder() + .table(ChapterSyncTable.TABLE) + .where(ChapterSyncTable.COLUMN_MANGA_ID + "=? AND " + + ChapterSyncTable.COLUMN_SYNC_ID + "=?") + .whereArgs(manga.id, sync.getId()) + .build()) + .prepare(); + } + + public PreparedPutObject insertChapterSync(ChapterSync chapter) { + return db.put() + .object(chapter) + .prepare(); + } + + public PreparedDeleteObject deleteChapterSync(ChapterSync chapter) { + return db.delete() + .object(chapter) + .prepare(); + } } diff --git a/app/src/main/java/eu/kanade/mangafeed/data/database/DbOpenHelper.java b/app/src/main/java/eu/kanade/mangafeed/data/database/DbOpenHelper.java index bfebf5ed8..d9780c5d3 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/database/DbOpenHelper.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/database/DbOpenHelper.java @@ -5,13 +5,14 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.support.annotation.NonNull; +import eu.kanade.mangafeed.data.database.tables.ChapterSyncTable; import eu.kanade.mangafeed.data.database.tables.ChapterTable; import eu.kanade.mangafeed.data.database.tables.MangaTable; public class DbOpenHelper extends SQLiteOpenHelper { public static final String DATABASE_NAME = "mangafeed.db"; - public static final int DATABASE_VERSION = 1; + public static final int DATABASE_VERSION = 2; public DbOpenHelper(@NonNull Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); @@ -25,7 +26,8 @@ public class DbOpenHelper extends SQLiteOpenHelper { @Override public void onUpgrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { - // no impl + if (oldVersion == 1) + db.execSQL(ChapterSyncTable.getCreateTableQuery()); } @Override diff --git a/app/src/main/java/eu/kanade/mangafeed/data/database/models/ChapterSync.java b/app/src/main/java/eu/kanade/mangafeed/data/database/models/ChapterSync.java new file mode 100644 index 000000000..2f4314e21 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/data/database/models/ChapterSync.java @@ -0,0 +1,35 @@ +package eu.kanade.mangafeed.data.database.models; + +import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteColumn; +import com.pushtorefresh.storio.sqlite.annotations.StorIOSQLiteType; + +import eu.kanade.mangafeed.data.chaptersync.BaseChapterSync; +import eu.kanade.mangafeed.data.database.tables.ChapterSyncTable; + +@StorIOSQLiteType(table = ChapterSyncTable.TABLE) +public class ChapterSync { + + @StorIOSQLiteColumn(name = ChapterSyncTable.COLUMN_ID, key = true) + public long id; + + @StorIOSQLiteColumn(name = ChapterSyncTable.COLUMN_MANGA_ID) + public long manga_id; + + @StorIOSQLiteColumn(name = ChapterSyncTable.COLUMN_SYNC_ID) + public long sync_id; + + @StorIOSQLiteColumn(name = ChapterSyncTable.COLUMN_REMOTE_ID) + public long remote_id; + + @StorIOSQLiteColumn(name = ChapterSyncTable.COLUMN_TITLE) + public String title; + + @StorIOSQLiteColumn(name = ChapterSyncTable.COLUMN_LAST_CHAPTER_READ) + public int last_chapter_read; + + public static ChapterSync create(BaseChapterSync sync) { + ChapterSync chapter = new ChapterSync(); + chapter.sync_id = sync.getId(); + return chapter; + } +} diff --git a/app/src/main/java/eu/kanade/mangafeed/data/database/tables/ChapterSyncTable.java b/app/src/main/java/eu/kanade/mangafeed/data/database/tables/ChapterSyncTable.java new file mode 100644 index 000000000..98bb75501 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/data/database/tables/ChapterSyncTable.java @@ -0,0 +1,35 @@ +package eu.kanade.mangafeed.data.database.tables; + +import android.support.annotation.NonNull; + +public class ChapterSyncTable { + + public static final String TABLE = "chapter_sync"; + + public static final String COLUMN_ID = "_id"; + + public static final String COLUMN_MANGA_ID = "manga_id"; + + public static final String COLUMN_SYNC_ID = "sync_id"; + + public static final String COLUMN_REMOTE_ID = "remote_id"; + + public static final String COLUMN_TITLE = "title"; + + public static final String COLUMN_LAST_CHAPTER_READ = "last_chapter_read"; + + @NonNull + public static String getCreateTableQuery() { + return "CREATE TABLE " + TABLE + "(" + + COLUMN_ID + " INTEGER NOT NULL PRIMARY KEY, " + + COLUMN_MANGA_ID + " INTEGER NOT NULL, " + + COLUMN_SYNC_ID + " INTEGER NOT NULL, " + + COLUMN_REMOTE_ID + " INTEGER NOT NULL, " + + COLUMN_TITLE + " TEXT NOT NULL, " + + COLUMN_LAST_CHAPTER_READ + " INTEGER NOT NULL, " + + "FOREIGN KEY(" + COLUMN_MANGA_ID + ") REFERENCES " + MangaTable.TABLE + "(" + MangaTable.COLUMN_ID + ") " + + "ON DELETE CASCADE" + + ");"; + } + +} diff --git a/app/src/main/java/eu/kanade/mangafeed/data/preference/PreferencesHelper.java b/app/src/main/java/eu/kanade/mangafeed/data/preference/PreferencesHelper.java index 8197163f4..8d0a4b5c9 100644 --- a/app/src/main/java/eu/kanade/mangafeed/data/preference/PreferencesHelper.java +++ b/app/src/main/java/eu/kanade/mangafeed/data/preference/PreferencesHelper.java @@ -8,6 +8,7 @@ import com.f2prateek.rx.preferences.Preference; import com.f2prateek.rx.preferences.RxSharedPreferences; import eu.kanade.mangafeed.R; +import eu.kanade.mangafeed.data.chaptersync.BaseChapterSync; import eu.kanade.mangafeed.data.source.base.Source; import eu.kanade.mangafeed.util.DiskUtils; import rx.Observable; @@ -20,6 +21,8 @@ public class PreferencesHelper { private static final String SOURCE_ACCOUNT_USERNAME = "pref_source_username_"; private static final String SOURCE_ACCOUNT_PASSWORD = "pref_source_password_"; + private static final String CHAPTERSYNC_ACCOUNT_USERNAME = "pref_chaptersync_username_"; + private static final String CHAPTERSYNC_ACCOUNT_PASSWORD = "pref_chaptersync_password_"; public PreferencesHelper(Context context) { this.context = context; @@ -84,6 +87,21 @@ public class PreferencesHelper { .apply(); } + public String getChapterSyncUsername(BaseChapterSync sync) { + return prefs.getString(CHAPTERSYNC_ACCOUNT_USERNAME + sync.getId(), ""); + } + + public String getChapterSyncPassword(BaseChapterSync sync) { + return prefs.getString(CHAPTERSYNC_ACCOUNT_PASSWORD + sync.getId(), ""); + } + + public void setChapterSyncCredentials(BaseChapterSync sync, String username, String password) { + prefs.edit() + .putString(CHAPTERSYNC_ACCOUNT_USERNAME + sync.getId(), username) + .putString(CHAPTERSYNC_ACCOUNT_PASSWORD + sync.getId(), password) + .apply(); + } + public String getDownloadsDirectory() { return prefs.getString(getKey(R.string.pref_download_directory_key), DiskUtils.getStorageDirectories(context)[0]); diff --git a/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java b/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java index 893c4cfbc..53c98da6d 100644 --- a/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java +++ b/app/src/main/java/eu/kanade/mangafeed/injection/component/AppComponent.java @@ -5,6 +5,7 @@ import android.app.Application; import javax.inject.Singleton; import dagger.Component; +import eu.kanade.mangafeed.data.chaptersync.MyAnimeList; import eu.kanade.mangafeed.data.download.DownloadService; import eu.kanade.mangafeed.data.sync.LibraryUpdateService; import eu.kanade.mangafeed.injection.module.AppModule; @@ -12,9 +13,11 @@ import eu.kanade.mangafeed.injection.module.DataModule; import eu.kanade.mangafeed.ui.catalogue.CataloguePresenter; import eu.kanade.mangafeed.ui.download.DownloadPresenter; import eu.kanade.mangafeed.ui.library.LibraryPresenter; +import eu.kanade.mangafeed.ui.manga.MangaActivity; import eu.kanade.mangafeed.ui.manga.MangaPresenter; import eu.kanade.mangafeed.ui.manga.chapter.ChaptersPresenter; import eu.kanade.mangafeed.ui.manga.info.MangaInfoPresenter; +import eu.kanade.mangafeed.ui.manga.myanimelist.MyAnimeListPresenter; import eu.kanade.mangafeed.ui.reader.ReaderPresenter; import eu.kanade.mangafeed.ui.catalogue.SourcePresenter; import eu.kanade.mangafeed.data.source.base.Source; @@ -39,13 +42,17 @@ public interface AppComponent { void inject(ChaptersPresenter chaptersPresenter); void inject(ReaderPresenter readerPresenter); void inject(DownloadPresenter downloadPresenter); + void inject(MyAnimeListPresenter myAnimeListPresenter); void inject(ReaderActivity readerActivity); + void inject(MangaActivity mangaActivity); void inject(SettingsAccountsFragment settingsAccountsFragment); void inject(SettingsDownloadsFragment settingsDownloadsFragment); void inject(Source source); + void inject(MyAnimeList myAnimeList); + void inject(LibraryUpdateService libraryUpdateService); void inject(DownloadService downloadService); diff --git a/app/src/main/java/eu/kanade/mangafeed/injection/module/DataModule.java b/app/src/main/java/eu/kanade/mangafeed/injection/module/DataModule.java index a02d567c7..a373c0870 100644 --- a/app/src/main/java/eu/kanade/mangafeed/injection/module/DataModule.java +++ b/app/src/main/java/eu/kanade/mangafeed/injection/module/DataModule.java @@ -7,6 +7,7 @@ import javax.inject.Singleton; import dagger.Module; import dagger.Provides; import eu.kanade.mangafeed.data.cache.CacheManager; +import eu.kanade.mangafeed.data.chaptersync.ChapterSyncManager; import eu.kanade.mangafeed.data.database.DatabaseHelper; import eu.kanade.mangafeed.data.download.DownloadManager; import eu.kanade.mangafeed.data.network.NetworkHelper; @@ -56,4 +57,10 @@ public class DataModule { return new DownloadManager(app, sourceManager, preferences); } + @Provides + @Singleton + ChapterSyncManager provideChapterSyncManager(Application app) { + return new ChapterSyncManager(app); + } + } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/manga/MangaActivity.java b/app/src/main/java/eu/kanade/mangafeed/ui/manga/MangaActivity.java index 612a48eed..3036134e8 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/manga/MangaActivity.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/manga/MangaActivity.java @@ -11,13 +11,19 @@ import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.view.ViewPager; import android.support.v7.widget.Toolbar; +import javax.inject.Inject; + import butterknife.Bind; import butterknife.ButterKnife; +import eu.kanade.mangafeed.App; import eu.kanade.mangafeed.R; +import eu.kanade.mangafeed.data.chaptersync.ChapterSyncManager; import eu.kanade.mangafeed.data.database.models.Manga; +import eu.kanade.mangafeed.data.preference.PreferencesHelper; import eu.kanade.mangafeed.ui.base.activity.BaseRxActivity; import eu.kanade.mangafeed.ui.manga.chapter.ChaptersFragment; import eu.kanade.mangafeed.ui.manga.info.MangaInfoFragment; +import eu.kanade.mangafeed.ui.manga.myanimelist.MyAnimeListFragment; import nucleus.factory.RequiresPresenter; @RequiresPresenter(MangaPresenter.class) @@ -27,6 +33,9 @@ public class MangaActivity extends BaseRxActivity { @Bind(R.id.tabs) TabLayout tabs; @Bind(R.id.view_pager) ViewPager view_pager; + @Inject PreferencesHelper preferences; + @Inject ChapterSyncManager chapterSyncManager; + private MangaDetailAdapter adapter; private long manga_id; private boolean is_online; @@ -43,6 +52,7 @@ public class MangaActivity extends BaseRxActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + App.get(this).getComponent().inject(this); setContentView(R.layout.activity_manga_detail); ButterKnife.bind(this); @@ -88,25 +98,31 @@ public class MangaActivity extends BaseRxActivity { class MangaDetailAdapter extends FragmentPagerAdapter { - final int PAGE_COUNT = 2; - private String tab_titles[]; + private int pageCount; + private String tabTitles[]; private Context context; final static int INFO_FRAGMENT = 0; final static int CHAPTERS_FRAGMENT = 1; + final static int MYANIMELIST_FRAGMENT = 2; public MangaDetailAdapter(FragmentManager fm, Context context) { super(fm); this.context = context; - tab_titles = new String[]{ + tabTitles = new String[]{ context.getString(R.string.manga_detail_tab), - context.getString(R.string.manga_chapters_tab) + context.getString(R.string.manga_chapters_tab), + "MAL" }; + + pageCount = 2; + if (chapterSyncManager.getMyAnimeList().isLogged()) + pageCount++; } @Override public int getCount() { - return PAGE_COUNT; + return pageCount; } @Override @@ -116,7 +132,8 @@ public class MangaActivity extends BaseRxActivity { return MangaInfoFragment.newInstance(); case CHAPTERS_FRAGMENT: return ChaptersFragment.newInstance(); - + case MYANIMELIST_FRAGMENT: + return MyAnimeListFragment.newInstance(); default: return null; } @@ -125,7 +142,7 @@ public class MangaActivity extends BaseRxActivity { @Override public CharSequence getPageTitle(int position) { // Generate title based on item position - return tab_titles[position]; + return tabTitles[position]; } } diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/manga/info/MangaInfoFragment.java b/app/src/main/java/eu/kanade/mangafeed/ui/manga/info/MangaInfoFragment.java index e73f35c0a..ffcd818af 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/manga/info/MangaInfoFragment.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/manga/info/MangaInfoFragment.java @@ -2,10 +2,6 @@ package eu.kanade.mangafeed.ui.manga.info; import android.os.Bundle; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.Button; @@ -52,28 +48,12 @@ public class MangaInfoFragment extends BaseRxFragment { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_manga_info, container, false); ButterKnife.bind(this, view); - favoriteBtn.setOnTouchListener((v, event) -> { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - getPresenter().toggleFavorite(); - return true; - } - return false; + favoriteBtn.setOnClickListener(v -> { + getPresenter().toggleFavorite(); }); - getPresenter().initFavoriteText(); + return view; - - - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - return super.onOptionsItemSelected(item); } public void setMangaInfo(Manga manga) { diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/manga/myanimelist/MyAnimeListDialogFragment.java b/app/src/main/java/eu/kanade/mangafeed/ui/manga/myanimelist/MyAnimeListDialogFragment.java new file mode 100644 index 000000000..206398d8a --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/ui/manga/myanimelist/MyAnimeListDialogFragment.java @@ -0,0 +1,99 @@ +package eu.kanade.mangafeed.ui.manga.myanimelist; + +import android.app.Dialog; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.List; + +import butterknife.Bind; +import butterknife.ButterKnife; +import eu.kanade.mangafeed.R; +import eu.kanade.mangafeed.data.database.models.ChapterSync; +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 { + + @Bind(R.id.myanimelist_search_field) EditText searchText; + @Bind(R.id.myanimelist_search_button) Button searchButton; + @Bind(R.id.myanimelist_search_results) ListView searchResults; + + private EasyAdapter adapter; + private MyAnimeListFragment fragment; + private ChapterSync selectedItem; + + public static MyAnimeListDialogFragment newInstance(MyAnimeListFragment parentFragment) { + MyAnimeListDialogFragment dialog = new MyAnimeListDialogFragment(); + dialog.setParentFragment(parentFragment); + return dialog; + } + + @Override + public Dialog onCreateDialog(Bundle savedState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + + LayoutInflater inflater = getActivity().getLayoutInflater(); + + View view = inflater.inflate(R.layout.dialog_myanimelist_search, null); + ButterKnife.bind(this, view); + + + builder.setView(view) + .setPositiveButton(R.string.button_ok, (dialog, which) -> onPositiveButtonClick()) + .setNegativeButton(R.string.button_cancel, (dialog, which) -> {}); + + searchButton.setOnClickListener(v -> + fragment.getPresenter().searchManga(searchText.getText().toString())); + + searchResults.setOnItemClickListener((parent, viewList, position, id) -> + selectedItem = adapter.getItem(position)); + + adapter = new EasyAdapter<>(getActivity(), ResultViewHolder.class); + + searchResults.setAdapter(adapter); + + return builder.create(); + } + + private void onPositiveButtonClick() { + if (adapter != null && selectedItem != null) { + fragment.getPresenter().registerManga(selectedItem); + } + } + + public void setResults(List results) { + selectedItem = null; + adapter.setItems(results); + } + + public void setParentFragment(MyAnimeListFragment fragment) { + this.fragment = fragment; + } + + @LayoutId(R.layout.dialog_myanimelist_search_item) + public static class ResultViewHolder extends ItemViewHolder { + + @ViewId(R.id.myanimelist_result_title) TextView title; + + public ResultViewHolder(View view) { + super(view); + } + + @Override + public void onSetValues(ChapterSync chapter, PositionInfo positionInfo) { + title.setText(chapter.title); + } + } + +} diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/manga/myanimelist/MyAnimeListFragment.java b/app/src/main/java/eu/kanade/mangafeed/ui/manga/myanimelist/MyAnimeListFragment.java new file mode 100644 index 000000000..c2ec9b042 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/ui/manga/myanimelist/MyAnimeListFragment.java @@ -0,0 +1,85 @@ +package eu.kanade.mangafeed.ui.manga.myanimelist; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import java.util.List; + +import butterknife.Bind; +import butterknife.ButterKnife; +import eu.kanade.mangafeed.R; +import eu.kanade.mangafeed.data.database.models.ChapterSync; +import eu.kanade.mangafeed.ui.base.fragment.BaseRxFragment; +import nucleus.factory.RequiresPresenter; + +@RequiresPresenter(MyAnimeListPresenter.class) +public class MyAnimeListFragment extends BaseRxFragment { + + @Bind(R.id.myanimelist_title) TextView title; + @Bind(R.id.myanimelist_last_chapter_read) EditText lastChapterRead; + @Bind(R.id.update_button) Button updateButton; + + private MyAnimeListDialogFragment dialog; + + public static MyAnimeListFragment newInstance() { + return new MyAnimeListFragment(); + } + + @Override + public void onCreate(Bundle bundle) { + super.onCreate(bundle); + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_myanimelist, container, false); + ButterKnife.bind(this, view); + + updateButton.setOnClickListener(v -> getPresenter().updateLastChapter( + Integer.parseInt(lastChapterRead.getText().toString()))); + + return view; + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.myanimelist, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.myanimelist_edit: + showSearchDialog(); + return true; + } + return super.onOptionsItemSelected(item); + } + + public void setChapterSync(ChapterSync chapterSync) { + title.setText(chapterSync.title); + lastChapterRead.setText(chapterSync.last_chapter_read + ""); + } + + private void showSearchDialog() { + if (dialog == null) + dialog = MyAnimeListDialogFragment.newInstance(this); + + dialog.show(getActivity().getSupportFragmentManager(), "search"); + } + + public void onSearchResults(List results) { + if (dialog != null) + dialog.setResults(results); + } +} diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/manga/myanimelist/MyAnimeListPresenter.java b/app/src/main/java/eu/kanade/mangafeed/ui/manga/myanimelist/MyAnimeListPresenter.java new file mode 100644 index 000000000..d19d4c487 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/ui/manga/myanimelist/MyAnimeListPresenter.java @@ -0,0 +1,105 @@ +package eu.kanade.mangafeed.ui.manga.myanimelist; + +import android.os.Bundle; + +import javax.inject.Inject; + +import eu.kanade.mangafeed.data.chaptersync.ChapterSyncManager; +import eu.kanade.mangafeed.data.chaptersync.MyAnimeList; +import eu.kanade.mangafeed.data.database.DatabaseHelper; +import eu.kanade.mangafeed.data.database.models.ChapterSync; +import eu.kanade.mangafeed.data.database.models.Manga; +import eu.kanade.mangafeed.ui.base.presenter.BasePresenter; +import eu.kanade.mangafeed.util.EventBusHook; +import rx.Observable; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; +import timber.log.Timber; + +public class MyAnimeListPresenter extends BasePresenter { + + @Inject DatabaseHelper db; + @Inject ChapterSyncManager syncManager; + + private MyAnimeList myAnimeList; + private Manga manga; + private ChapterSync chapterSync; + + private String query; + + private Subscription updateSubscription; + + private static final int GET_CHAPTER_SYNC = 1; + private static final int GET_SEARCH_RESULTS = 2; + + @Override + protected void onCreate(Bundle savedState) { + super.onCreate(savedState); + + myAnimeList = syncManager.getMyAnimeList(); + + restartableLatestCache(GET_CHAPTER_SYNC, + () -> db.getChapterSync(manga, myAnimeList).createObservable() + .flatMap(Observable::from) + .doOnNext(chapterSync -> this.chapterSync = chapterSync) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()), + MyAnimeListFragment::setChapterSync); + + restartableLatestCache(GET_SEARCH_RESULTS, + () -> myAnimeList.search(query) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()), + (view, results) -> { + view.onSearchResults(results); + }, (view, error) -> { + Timber.e(error.getMessage()); + }); + + } + + @Override + protected void onTakeView(MyAnimeListFragment view) { + super.onTakeView(view); + registerForStickyEvents(); + } + + @Override + protected void onDropView() { + unregisterForEvents(); + super.onDropView(); + } + + @EventBusHook + public void onEventMainThread(Manga manga) { + this.manga = manga; + start(GET_CHAPTER_SYNC); + } + + public void updateLastChapter(int chapterNumber) { + if (updateSubscription != null) + remove(updateSubscription); + + chapterSync.last_chapter_read = chapterNumber; + + add(updateSubscription = myAnimeList.update(chapterSync) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(response -> {}, + error -> { + Timber.e(error.getMessage()); + } + )); + } + + public void searchManga(String query) { + this.query = query; + start(GET_SEARCH_RESULTS); + } + + public void registerManga(ChapterSync selectedManga) { + selectedManga.manga_id = manga.id; + db.insertChapterSync(selectedManga).executeAsBlocking(); + } +} diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/setting/LoginDialogPreference.java b/app/src/main/java/eu/kanade/mangafeed/ui/setting/LoginDialogPreference.java deleted file mode 100644 index dfbfc07ff..000000000 --- a/app/src/main/java/eu/kanade/mangafeed/ui/setting/LoginDialogPreference.java +++ /dev/null @@ -1,124 +0,0 @@ -package eu.kanade.mangafeed.ui.setting; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.preference.DialogPreference; -import android.text.method.PasswordTransformationMethod; -import android.view.View; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.TextView; - -import com.dd.processbutton.iml.ActionProcessButton; - -import butterknife.Bind; -import butterknife.ButterKnife; -import eu.kanade.mangafeed.R; -import eu.kanade.mangafeed.data.preference.PreferencesHelper; -import eu.kanade.mangafeed.data.source.base.Source; -import eu.kanade.mangafeed.util.ToastUtil; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.schedulers.Schedulers; - -public class LoginDialogPreference extends DialogPreference { - - @Bind(R.id.accounts_login) TextView title; - @Bind(R.id.username) EditText username; - @Bind(R.id.password) EditText password; - @Bind(R.id.show_password) CheckBox showPassword; - @Bind(R.id.login) ActionProcessButton loginBtn; - - private PreferencesHelper preferences; - private Source source; - private AlertDialog dialog; - private Subscription requestSubscription; - private Context context; - - public LoginDialogPreference(Context context, PreferencesHelper preferences, Source source) { - super(context, null); - this.context = context; - this.preferences = preferences; - this.source = source; - - setDialogLayoutResource(R.layout.pref_account_login); - } - - @Override - protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { - // Hide positive button - builder.setPositiveButton("", this); - } - - @Override - protected void onBindDialogView(View view) { - ButterKnife.bind(this, view); - - title.setText(getContext().getString(R.string.accounts_login_title, source.getName())); - - username.setText(preferences.getSourceUsername(source)); - password.setText(preferences.getSourcePassword(source)); - showPassword.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked) - password.setTransformationMethod(null); - else - password.setTransformationMethod(new PasswordTransformationMethod()); - }); - - loginBtn.setMode(ActionProcessButton.Mode.ENDLESS); - loginBtn.setOnClickListener(click -> checkLogin()); - - super.onBindDialogView(view); - } - - @Override - public void showDialog(Bundle state) { - super.showDialog(state); - dialog = ((AlertDialog) getDialog()); - } - - @Override - protected void onDialogClosed(boolean positiveResult) { - if (requestSubscription != null) - requestSubscription.unsubscribe(); - - if(!positiveResult) - return; - - preferences.setSourceCredentials(source, - username.getText().toString(), - password.getText().toString()); - } - - private void checkLogin() { - if (requestSubscription != null) - requestSubscription.unsubscribe(); - - if (username.getText().length() == 0 || password.getText().length() == 0) - return; - - loginBtn.setProgress(1); - - requestSubscription = source - .login(username.getText().toString(), password.getText().toString()) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(logged -> { - if (logged) { - // Simulate a positive button click and dismiss the dialog - onClick(dialog, DialogInterface.BUTTON_POSITIVE); - dialog.dismiss(); - ToastUtil.showShort(context, R.string.login_success); - } else { - loginBtn.setProgress(-1); - } - }, throwable -> { - loginBtn.setProgress(-1); - loginBtn.setText(R.string.unknown_error); - }); - - } - -} diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/setting/SettingsAccountsFragment.java b/app/src/main/java/eu/kanade/mangafeed/ui/setting/SettingsAccountsFragment.java index 95cec0106..97485b8af 100644 --- a/app/src/main/java/eu/kanade/mangafeed/ui/setting/SettingsAccountsFragment.java +++ b/app/src/main/java/eu/kanade/mangafeed/ui/setting/SettingsAccountsFragment.java @@ -1,6 +1,7 @@ package eu.kanade.mangafeed.ui.setting; import android.os.Bundle; +import android.preference.PreferenceCategory; import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; @@ -10,16 +11,21 @@ import javax.inject.Inject; import eu.kanade.mangafeed.App; import eu.kanade.mangafeed.R; +import eu.kanade.mangafeed.data.chaptersync.BaseChapterSync; +import eu.kanade.mangafeed.data.chaptersync.ChapterSyncManager; import eu.kanade.mangafeed.data.preference.PreferencesHelper; import eu.kanade.mangafeed.data.source.SourceManager; import eu.kanade.mangafeed.data.source.base.Source; import eu.kanade.mangafeed.ui.base.activity.BaseActivity; +import eu.kanade.mangafeed.ui.setting.dialog.ChapterSyncLoginDialog; +import eu.kanade.mangafeed.ui.setting.dialog.SourceLoginDialog; import rx.Observable; public class SettingsAccountsFragment extends PreferenceFragment { - @Inject SourceManager sourceManager; @Inject PreferencesHelper preferences; + @Inject SourceManager sourceManager; + @Inject ChapterSyncManager syncManager; public static SettingsAccountsFragment newInstance() { return new SettingsAccountsFragment(); @@ -35,13 +41,30 @@ public class SettingsAccountsFragment extends PreferenceFragment { List sourceAccounts = getSourcesWithLogin(); + PreferenceCategory sourceCategory = new PreferenceCategory(screen.getContext()); + sourceCategory.setTitle("Sources"); + screen.addPreference(sourceCategory); + for (Source source : sourceAccounts) { - LoginDialogPreference dialog = new LoginDialogPreference( + SourceLoginDialog dialog = new SourceLoginDialog( screen.getContext(), preferences, source); dialog.setTitle(source.getName()); - screen.addPreference(dialog); + sourceCategory.addPreference(dialog); } + + PreferenceCategory chapterSyncCategory = new PreferenceCategory(screen.getContext()); + chapterSyncCategory.setTitle("Sync"); + screen.addPreference(chapterSyncCategory); + + for (BaseChapterSync sync : syncManager.getChapterSyncServices()) { + ChapterSyncLoginDialog dialog = new ChapterSyncLoginDialog( + screen.getContext(), preferences, sync); + dialog.setTitle(sync.getName()); + + chapterSyncCategory.addPreference(dialog); + } + } @Override diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/setting/dialog/ChapterSyncLoginDialog.java b/app/src/main/java/eu/kanade/mangafeed/ui/setting/dialog/ChapterSyncLoginDialog.java new file mode 100644 index 000000000..286228265 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/ui/setting/dialog/ChapterSyncLoginDialog.java @@ -0,0 +1,74 @@ +package eu.kanade.mangafeed.ui.setting.dialog; + +import android.content.Context; +import android.content.DialogInterface; +import android.view.View; + +import eu.kanade.mangafeed.R; +import eu.kanade.mangafeed.data.chaptersync.BaseChapterSync; +import eu.kanade.mangafeed.data.preference.PreferencesHelper; +import eu.kanade.mangafeed.util.ToastUtil; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +public class ChapterSyncLoginDialog extends LoginDialogPreference { + + private BaseChapterSync sync; + + public ChapterSyncLoginDialog(Context context, PreferencesHelper preferences, BaseChapterSync sync) { + super(context, preferences); + this.sync = sync; + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + title.setText(getContext().getString(R.string.accounts_login_title, sync.getName())); + + username.setText(preferences.getChapterSyncUsername(sync)); + password.setText(preferences.getChapterSyncPassword(sync)); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + + if (positiveResult) { + preferences.setChapterSyncCredentials(sync, + username.getText().toString(), + password.getText().toString()); + } + } + + protected void checkLogin() { + if (requestSubscription != null) + requestSubscription.unsubscribe(); + + if (username.getText().length() == 0 || password.getText().length() == 0) + return; + + loginBtn.setProgress(1); + + requestSubscription = sync + .login(username.getText().toString(), password.getText().toString()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(logged -> { + if (logged) { + // Simulate a positive button click and dismiss the dialog + onClick(dialog, DialogInterface.BUTTON_POSITIVE); + dialog.dismiss(); + ToastUtil.showShort(context, R.string.login_success); + } else { + preferences.setChapterSyncCredentials(sync, "", ""); + loginBtn.setProgress(-1); + } + }, error -> { + loginBtn.setProgress(-1); + loginBtn.setText(R.string.unknown_error); + }); + + } + +} diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/setting/dialog/LoginDialogPreference.java b/app/src/main/java/eu/kanade/mangafeed/ui/setting/dialog/LoginDialogPreference.java new file mode 100644 index 000000000..cb93b6d5a --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/ui/setting/dialog/LoginDialogPreference.java @@ -0,0 +1,78 @@ +package eu.kanade.mangafeed.ui.setting.dialog; + +import android.app.AlertDialog; +import android.content.Context; +import android.os.Bundle; +import android.preference.DialogPreference; +import android.text.method.PasswordTransformationMethod; +import android.view.View; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.TextView; + +import com.dd.processbutton.iml.ActionProcessButton; + +import butterknife.Bind; +import butterknife.ButterKnife; +import eu.kanade.mangafeed.R; +import eu.kanade.mangafeed.data.preference.PreferencesHelper; +import rx.Subscription; + +public abstract class LoginDialogPreference extends DialogPreference { + + @Bind(R.id.accounts_login) TextView title; + @Bind(R.id.username) EditText username; + @Bind(R.id.password) EditText password; + @Bind(R.id.show_password) CheckBox showPassword; + @Bind(R.id.login) ActionProcessButton loginBtn; + + protected PreferencesHelper preferences; + protected AlertDialog dialog; + protected Subscription requestSubscription; + protected Context context; + + public LoginDialogPreference(Context context, PreferencesHelper preferences) { + super(context, null); + this.context = context; + this.preferences = preferences; + + setDialogLayoutResource(R.layout.pref_account_login); + } + + @Override + protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { + // Hide positive button + builder.setPositiveButton("", this); + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + ButterKnife.bind(this, view); + + showPassword.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) + password.setTransformationMethod(null); + else + password.setTransformationMethod(new PasswordTransformationMethod()); + }); + + loginBtn.setMode(ActionProcessButton.Mode.ENDLESS); + loginBtn.setOnClickListener(click -> checkLogin()); + } + + @Override + public void showDialog(Bundle state) { + super.showDialog(state); + dialog = ((AlertDialog) getDialog()); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + if (requestSubscription != null) + requestSubscription.unsubscribe(); + } + + protected abstract void checkLogin(); + +} diff --git a/app/src/main/java/eu/kanade/mangafeed/ui/setting/dialog/SourceLoginDialog.java b/app/src/main/java/eu/kanade/mangafeed/ui/setting/dialog/SourceLoginDialog.java new file mode 100644 index 000000000..cb7f40708 --- /dev/null +++ b/app/src/main/java/eu/kanade/mangafeed/ui/setting/dialog/SourceLoginDialog.java @@ -0,0 +1,74 @@ +package eu.kanade.mangafeed.ui.setting.dialog; + +import android.content.Context; +import android.content.DialogInterface; +import android.view.View; + +import eu.kanade.mangafeed.R; +import eu.kanade.mangafeed.data.preference.PreferencesHelper; +import eu.kanade.mangafeed.data.source.base.Source; +import eu.kanade.mangafeed.util.ToastUtil; +import rx.android.schedulers.AndroidSchedulers; +import rx.schedulers.Schedulers; + +public class SourceLoginDialog extends LoginDialogPreference { + + private Source source; + + public SourceLoginDialog(Context context, PreferencesHelper preferences, Source source) { + super(context, preferences); + this.source = source; + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + title.setText(getContext().getString(R.string.accounts_login_title, source.getName())); + + username.setText(preferences.getSourceUsername(source)); + password.setText(preferences.getSourcePassword(source)); + } + + @Override + protected void onDialogClosed(boolean positiveResult) { + super.onDialogClosed(positiveResult); + + if (positiveResult) { + preferences.setSourceCredentials(source, + username.getText().toString(), + password.getText().toString()); + } + } + + protected void checkLogin() { + if (requestSubscription != null) + requestSubscription.unsubscribe(); + + if (username.getText().length() == 0 || password.getText().length() == 0) + return; + + loginBtn.setProgress(1); + + requestSubscription = source + .login(username.getText().toString(), password.getText().toString()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(logged -> { + if (logged) { + // Simulate a positive button click and dismiss the dialog + onClick(dialog, DialogInterface.BUTTON_POSITIVE); + dialog.dismiss(); + ToastUtil.showShort(context, R.string.login_success); + } else { + preferences.setSourceCredentials(source, "", ""); + loginBtn.setProgress(-1); + } + }, error -> { + loginBtn.setProgress(-1); + loginBtn.setText(R.string.unknown_error); + }); + + } + +} diff --git a/app/src/main/res/drawable-hdpi/ic_create.png b/app/src/main/res/drawable-hdpi/ic_create.png new file mode 100644 index 0000000000000000000000000000000000000000..14419533255dd5e2c16eaa230a74aaaddcb04ad3 GIT binary patch literal 262 zcmV+h0r~!kP)!49*?(zYXWC}SepA^7>2*en zlN?fb0Jx)+K@$U);+Xx1aVU00EnrYb9#W`1)QyGS**wu$RF4A(%@LbakDSX$X%R1! z!F&TL5JcVW=9rmS92<&G?}ZQ*aksnZmeGB4a~- zW>{xTNWkb&Az^f=lQ40JU}DgKOWFh3Nth5sGdgq~sk!p)Tk{X-1%ZD4ma~Tb=l}o! M07*qoM6N<$g6!yLE&u=k literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-ldpi/ic_create.png b/app/src/main/res/drawable-ldpi/ic_create.png new file mode 100644 index 0000000000000000000000000000000000000000..e8e7053e7c1555e4cc298cf7b1a102e17624ec1a GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a)4E>9Q7kcwN$2@BLNT)6OHPubg9 z$5)514{0R2yb%yDYk-|QXC%Yd7&tVDqBGECw z(S-45nnNL9Q7kcwMpFYo1TP!MQKoc4LT zWC0&n`GNvJYh|-%!AveKFXqmj5U|(3NKrvyU+za{g^dS3G#+8wuBLaPoKd}KMYiwflo{$5_C8hJ@!afuvBR_S11Ewn6n)}; vQt7)lEUk_)Esm+X?R(>lpZ5Psw((Edyz=L)%jYzJu43?X^>bP0l+XkKIdoDD literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_create.png b/app/src/main/res/drawable-xhdpi/ic_create.png new file mode 100644 index 0000000000000000000000000000000000000000..611ecc5b5987e844f4c14b00b76a495c1f000938 GIT binary patch literal 299 zcmV+`0o4A9P)+LPuyu$`OtO@0w zbGnx#uMhsBW9ibad$bHax3 zZL23-`{#Rh^1K=c>0P3e`s5ZAYu8_z+wrB9y;tAct;(fYPrhpIl#Eu^zw@naG@pv= zmhnvUT)E}@#b8T+ucs1C@j}5tkAg1Re0+VNZ~y)!)pkoBS_TW5Ebz3@n#BKn`WDw_ zlQ~}6=NX#{-Y5th=V~gTMmrX8)YP>j?~a22WQ%mvv4F FO#pL|sIve7 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_create.png b/app/src/main/res/drawable-xxxhdpi/ic_create.png new file mode 100644 index 0000000000000000000000000000000000000000..7702b7dc8362a2fa483529e52cf1bc0489fdcde8 GIT binary patch literal 501 zcmVz(r`d0W~LKfg*@=(bI7PkT?Ss(om#uA{_6`>}20tm`!j0pPXH9 z6nm0s?9Y^d1SB8<2}nQ!67UxQxAG?6@+r@9IwD{r^LUZ7(Ez*oMgn8LiNKj}Ah71E z3B37A0&~8KfXlxY-KY@QuRxW+JqKpO$%w1a?s(z>T(}7DEP#!C$fbku#R1r+dI+x! zfH~7nxWNPV6K#Y${4)AGPfsZL8`*N2j!^JhiNjxxI-%e$KPoW6N=^x z3>$@ILeZUrVV|=^xWfOa*XJ)2zyMo*1A!&qP2kDj63+1ef4+LuP0f6SW1Hk#M0C;{Ipe6qv|35KBz8^r6?*-80`v64w9spIo z9YB_E1<>W&0EGD#0A;=&K$@=wwB$eFA2}n=*8r&V>j3in8h}239WX%t8eoY0HNX)0 z#{hKAj|X(lj|Ozlj|Ds;p81LcK=R`Np!_HREI$SS%#Q#-^V r_X9}s^F_D>Bp?9^NI(J-@V@~+rWv8_P0I|t00000NkvXXu0mjf9Pix7 literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/dialog_myanimelist_search.xml b/app/src/main/res/layout/dialog_myanimelist_search.xml new file mode 100644 index 000000000..70d6b2133 --- /dev/null +++ b/app/src/main/res/layout/dialog_myanimelist_search.xml @@ -0,0 +1,36 @@ + + + + + + + +